diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index dd231be4d440e57730430175c3662ed07e2e7178..f662e85b606053ca5cd0b81ea22b766bedb655e9 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -1,5 +1,7 @@ package net.sf.briar.api.db; +import java.io.IOException; +import java.security.SignatureException; import java.util.Map; import java.util.Set; @@ -49,7 +51,7 @@ public interface DatabaseComponent { * Generates a bundle of acknowledgements, subscriptions, and batches of * messages for the given contact. */ - Bundle generateBundle(ContactId c, BundleBuilder bundleBuilder) throws DbException; + Bundle generateBundle(ContactId c, BundleBuilder bundleBuilder) throws DbException, IOException, SignatureException; /** Returns the IDs of all contacts. */ Set<ContactId> getContacts() throws DbException; @@ -71,7 +73,7 @@ public interface DatabaseComponent { * messages received from the given contact. Some or all of the messages * in the bundle may be stored. */ - void receiveBundle(ContactId c, Bundle b) throws DbException; + void receiveBundle(ContactId c, Bundle b) throws DbException, IOException, SignatureException; /** Removes a contact (and all associated state) from the database. */ void removeContact(ContactId c) throws DbException; diff --git a/api/net/sf/briar/api/protocol/Batch.java b/api/net/sf/briar/api/protocol/Batch.java index eaa0ec261892a68a4f193cc2016bf238a96c1772..05dc321a295bc3bc004124a4fd9188951a99cbb7 100644 --- a/api/net/sf/briar/api/protocol/Batch.java +++ b/api/net/sf/briar/api/protocol/Batch.java @@ -1,16 +1,19 @@ package net.sf.briar.api.protocol; -/** A batch of messages up to CAPACITY bytes in total size. */ +/** A batch of messages up to MAX_SIZE bytes in total size. */ public interface Batch { - public static final long CAPACITY = 1024L * 1024L; + public static final int MAX_SIZE = 1024 * 1024; /** Returns the batch's unique identifier. */ BatchId getId(); - /** Returns the size of the batch in bytes. */ + /** Returns the size of the serialised batch in bytes. */ long getSize(); /** Returns the messages contained in the batch. */ Iterable<Message> getMessages(); + + /** Returns the sender's signature over the contents of the batch. */ + byte[] getSignature(); } \ No newline at end of file diff --git a/api/net/sf/briar/api/protocol/BatchBuilder.java b/api/net/sf/briar/api/protocol/BatchBuilder.java index cd5c345510bd2093b452eeb73c12399cf5d6dcbe..992657c9a98f3c6fd49f77dc375c787e238a211c 100644 --- a/api/net/sf/briar/api/protocol/BatchBuilder.java +++ b/api/net/sf/briar/api/protocol/BatchBuilder.java @@ -1,10 +1,15 @@ package net.sf.briar.api.protocol; +import java.security.SignatureException; + public interface BatchBuilder { /** Adds a message to the batch. */ void addMessage(Message m); + /** Sets the sender's signature over the contents of the batch. */ + void setSignature(byte[] sig); + /** Builds and returns the batch. */ - Batch build(); + Batch build() throws SignatureException; } diff --git a/api/net/sf/briar/api/protocol/Bundle.java b/api/net/sf/briar/api/protocol/Bundle.java index 7465c3b56e27debc2e484c2ae7337b5d3656cafb..82ff0f86d906ebe3d97157aad4ae584ea16c3ea5 100644 --- a/api/net/sf/briar/api/protocol/Bundle.java +++ b/api/net/sf/briar/api/protocol/Bundle.java @@ -1,28 +1,21 @@ package net.sf.briar.api.protocol; -import java.util.Map; +import java.io.IOException; +import java.security.SignatureException; -/** A bundle of acknowledgements, subscriptions, and batches of messages. */ +/** + * A bundle of acknowledgements, subscriptions, transport details and batches. + */ public interface Bundle { - /** Returns the bundle's unique identifier. */ - BundleId getId(); + /** Returns the size of the serialised bundle in bytes. */ + long getSize() throws IOException; - /** Returns the bundle's capacity in bytes. */ - long getCapacity(); + /** Returns the bundle's header. */ + Header getHeader() throws IOException, SignatureException; - /** Returns the bundle's size in bytes. */ - long getSize(); - - /** Returns the acknowledgements contained in the bundle. */ - Iterable<BatchId> getAcks(); - - /** Returns the subscriptions contained in the bundle. */ - Iterable<GroupId> getSubscriptions(); - - /** Returns the transport details contained in the bundle. */ - Map<String, String> getTransports(); - - /** Returns the batches of messages contained in the bundle. */ - Iterable<Batch> getBatches(); + /** + * Returns the next batch of messages, or null if there are no more batches. + */ + Batch getNextBatch() throws IOException, SignatureException; } diff --git a/api/net/sf/briar/api/protocol/BundleBuilder.java b/api/net/sf/briar/api/protocol/BundleBuilder.java index f95ef602dc9356c61ef394471b095e8ae5fa47ed..7f7c837719c4d87d07b46ec1d685f734e5608ed9 100644 --- a/api/net/sf/briar/api/protocol/BundleBuilder.java +++ b/api/net/sf/briar/api/protocol/BundleBuilder.java @@ -1,22 +1,18 @@ package net.sf.briar.api.protocol; +import java.io.IOException; + public interface BundleBuilder { /** Returns the bundle's capacity in bytes. */ - long getCapacity(); - - /** Adds an acknowledgement to the bundle. */ - void addAck(BatchId b); - - /** Adds a subscription to the bundle. */ - void addSubscription(GroupId g); + long getCapacity() throws IOException; - /** Adds a transport detail to the bundle. */ - void addTransport(String key, String value); + /** Adds a header to the bundle. */ + void addHeader(Header h) throws IOException; /** Adds a batch of messages to the bundle. */ - void addBatch(Batch b); + void addBatch(Batch b) throws IOException; /** Builds and returns the bundle. */ - Bundle build(); + Bundle build() throws IOException; } diff --git a/api/net/sf/briar/api/protocol/Header.java b/api/net/sf/briar/api/protocol/Header.java new file mode 100644 index 0000000000000000000000000000000000000000..ce3c581a0740198eed30475c0bf47ece526ee6a0 --- /dev/null +++ b/api/net/sf/briar/api/protocol/Header.java @@ -0,0 +1,28 @@ +package net.sf.briar.api.protocol; + +import java.util.Map; +import java.util.Set; + +/** A bundle header up to MAX_SIZE bytes in total size. */ +public interface Header { + + static final int MAX_SIZE = 1024 * 1024; + + // FIXME: Remove BundleId when refactoring is complete + BundleId getId(); + + /** Returns the size of the serialised header in bytes. */ + long getSize(); + + /** Returns the acknowledgements contained in the header. */ + Set<BatchId> getAcks(); + + /** Returns the subscriptions contained in the header. */ + Set<GroupId> getSubscriptions(); + + /** Returns the transport details contained in the header. */ + Map<String, String> getTransports(); + + /** Returns the sender's signature over the contents of the header. */ + byte[] getSignature(); +} diff --git a/api/net/sf/briar/api/protocol/HeaderBuilder.java b/api/net/sf/briar/api/protocol/HeaderBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..4c00f8218fab97ecee1e018b94b98c7eba1f6ad7 --- /dev/null +++ b/api/net/sf/briar/api/protocol/HeaderBuilder.java @@ -0,0 +1,24 @@ +package net.sf.briar.api.protocol; + +import java.io.IOException; +import java.security.SignatureException; +import java.util.Map; +import java.util.Set; + +public interface HeaderBuilder { + + /** Adds acknowledgements to the header. */ + void addAcks(Set<BatchId> acks) throws IOException; + + /** Adds subscriptions to the header. */ + void addSubscriptions(Set<GroupId> subs) throws IOException; + + /** Adds transport details to the header. */ + void addTransports(Map<String, String> transports) throws IOException; + + /** Sets the sender's signature over the contents of the header. */ + void setSignature(byte[] sig) throws IOException; + + /** Builds and returns the header. */ + Header build() throws SignatureException; +} diff --git a/api/net/sf/briar/api/protocol/MessageParser.java b/api/net/sf/briar/api/protocol/MessageParser.java new file mode 100644 index 0000000000000000000000000000000000000000..95a44e4cdd3528501098983de80ef60e31747a21 --- /dev/null +++ b/api/net/sf/briar/api/protocol/MessageParser.java @@ -0,0 +1,10 @@ +package net.sf.briar.api.protocol; + +import java.security.SignatureException; + +import net.sf.briar.api.serial.FormatException; + +public interface MessageParser { + + Message parseMessage(byte[] body) throws FormatException, SignatureException; +} diff --git a/api/net/sf/briar/api/protocol/UniqueId.java b/api/net/sf/briar/api/protocol/UniqueId.java index 3eb9cdd15d8eb4d9beaaf620d6a903a699c62c91..22f57dfe2b80f5a093bc859abc48d5dfe6fca04c 100644 --- a/api/net/sf/briar/api/protocol/UniqueId.java +++ b/api/net/sf/briar/api/protocol/UniqueId.java @@ -2,7 +2,9 @@ package net.sf.briar.api.protocol; import java.util.Arrays; -public abstract class UniqueId { +import net.sf.briar.api.serial.Raw; + +public abstract class UniqueId implements Raw { public static final int LENGTH = 32; diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index 49427ce337d991e3917380e6b8b7d06b6a4c2e6c..4fc737634d40e8abcced69301854d4d57e8663e0 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -10,6 +10,7 @@ import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.Status; import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.HeaderBuilder; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; @@ -27,6 +28,7 @@ DatabaseCleaner.Callback { protected final Database<Txn> db; protected final DatabaseCleaner cleaner; + protected final Provider<HeaderBuilder> headerBuilderProvider; protected final Provider<BatchBuilder> batchBuilderProvider; private final Object spaceLock = new Object(); @@ -36,9 +38,11 @@ DatabaseCleaner.Callback { private volatile boolean writesAllowed = true; DatabaseComponentImpl(Database<Txn> db, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { this.db = db; this.cleaner = cleaner; + this.headerBuilderProvider = headerBuilderProvider; this.batchBuilderProvider = batchBuilderProvider; } diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java index a5ec477985954221a7442c30ebcb27d5bd3ff1d3..8ab59f9335aeac53bf75a3565e3ef09f08e56706 100644 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java @@ -1,9 +1,10 @@ package net.sf.briar.db; +import java.io.IOException; +import java.security.SignatureException; import java.util.HashSet; import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; @@ -20,6 +21,8 @@ import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.Bundle; import net.sf.briar.api.protocol.BundleBuilder; import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.HeaderBuilder; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; @@ -55,8 +58,9 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { @Inject ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { - super(db, cleaner, batchBuilderProvider); + super(db, cleaner, headerBuilderProvider, batchBuilderProvider); } public void close() throws DbException { @@ -187,23 +191,22 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } public Bundle generateBundle(ContactId c, BundleBuilder b) - throws DbException { + throws DbException, IOException, SignatureException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c); - // Ack all batches received from c + HeaderBuilder h; + // Add acks contactLock.readLock().lock(); try { if(!containsContact(c)) throw new NoSuchContactException(); + h = headerBuilderProvider.get(); messageStatusLock.writeLock().lock(); try { Txn txn = db.startTransaction(); try { - int numAcks = 0; - for(BatchId ack : db.removeBatchesToAck(txn, c)) { - b.addAck(ack); - numAcks++; - } + Set<BatchId> acks = db.removeBatchesToAck(txn, c); + h.addAcks(acks); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + numAcks + " acks"); + LOG.fine("Added " + acks.size() + " acks"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -215,7 +218,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } finally { contactLock.readLock().unlock(); } - // Add a list of subscriptions + // Add subscriptions contactLock.readLock().lock(); try { if(!containsContact(c)) throw new NoSuchContactException(); @@ -223,13 +226,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { try { Txn txn = db.startTransaction(); try { - int numSubs = 0; - for(GroupId g : db.getSubscriptions(txn)) { - b.addSubscription(g); - numSubs++; - } + Set<GroupId> subs = db.getSubscriptions(txn); + h.addSubscriptions(subs); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + numSubs + " subscriptions"); + LOG.fine("Added " + subs.size() + " subscriptions"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -249,14 +249,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { try { Txn txn = db.startTransaction(); try { - int numTransports = 0; Map<String, String> transports = db.getTransports(txn); - for(Entry<String, String> e : transports.entrySet()) { - b.addTransport(e.getKey(), e.getValue()); - numTransports++; - } + h.addTransports(transports); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + numTransports + " transports"); + LOG.fine("Added " + transports.size() + " transports"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -268,8 +264,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } finally { contactLock.readLock().unlock(); } - // Add as many messages as possible to the bundle + // Sign the header and add it to the bundle + Header header = h.build(); long capacity = b.getCapacity(); + capacity -= header.getSize(); + b.addHeader(header); + // Add as many messages as possible to the bundle while(true) { Batch batch = fillBatch(c, capacity); if(batch == null) break; // No more messages to send @@ -278,7 +278,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { capacity -= size; // If the batch is less than half full, stop trying - there may be // more messages trickling in but we can't wait forever - if(size * 2 < Batch.CAPACITY) break; + if(size * 2 < Batch.MAX_SIZE) break; } Bundle bundle = b.build(); if(LOG.isLoggable(Level.FINE)) @@ -287,20 +287,20 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { return bundle; } - private Batch fillBatch(ContactId c, long capacity) throws DbException { + private Batch fillBatch(ContactId c, long capacity) throws DbException, + SignatureException { contactLock.readLock().lock(); try { if(!containsContact(c)) throw new NoSuchContactException(); messageLock.readLock().lock(); try { Set<MessageId> sent; - BatchBuilder b; Batch batch; messageStatusLock.readLock().lock(); try { Txn txn = db.startTransaction(); try { - capacity = Math.min(capacity, Batch.CAPACITY); + capacity = Math.min(capacity, Batch.MAX_SIZE); Iterator<MessageId> it = db.getSendableMessages(txn, c, capacity).iterator(); if(!it.hasNext()) { @@ -308,7 +308,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { return null; // No more messages to send } sent = new HashSet<MessageId>(); - b = batchBuilderProvider.get(); + BatchBuilder b = batchBuilderProvider.get(); while(it.hasNext()) { MessageId m = it.next(); b.addMessage(db.getMessage(txn, m)); @@ -319,6 +319,9 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } catch(DbException e) { db.abortTransaction(txn); throw e; + } catch(SignatureException e) { + db.abortTransaction(txn); + throw e; } } finally { messageStatusLock.readLock().unlock(); @@ -438,21 +441,23 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } - public void receiveBundle(ContactId c, Bundle b) throws DbException { + public void receiveBundle(ContactId c, Bundle b) throws DbException, + IOException, SignatureException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Received bundle from " + c + ", " + b.getSize() + " bytes"); + Header h; // Mark all messages in acked batches as seen contactLock.readLock().lock(); try { if(!containsContact(c)) throw new NoSuchContactException(); + h = b.getHeader(); messageLock.readLock().lock(); try { messageStatusLock.writeLock().lock(); try { - int acks = 0; - for(BatchId ack : b.getAcks()) { - acks++; + Set<BatchId> acks = h.getAcks(); + for(BatchId ack : acks) { Txn txn = db.startTransaction(); try { db.removeAckedBatch(txn, c, ack); @@ -463,7 +468,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + acks + " acks"); + LOG.fine("Received " + acks.size() + " acks"); } finally { messageStatusLock.writeLock().unlock(); } @@ -481,14 +486,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { try { Txn txn = db.startTransaction(); try { + // FIXME: Replace clearSubs and addSub with setSubs db.clearSubscriptions(txn, c); - int subs = 0; - for(GroupId g : b.getSubscriptions()) { - subs++; - db.addSubscription(txn, c, g); - } + Set<GroupId> subs = h.getSubscriptions(); + for(GroupId sub : subs) db.addSubscription(txn, c, sub); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + subs + " subscriptions"); + LOG.fine("Received " + subs.size() + " subscriptions"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -508,7 +511,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { try { Txn txn = db.startTransaction(); try { - db.setTransports(txn, c, b.getTransports()); + Map<String, String> transports = h.getTransports(); + db.setTransports(txn, c, transports); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Received " + transports.size() + + " transports"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -522,7 +529,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } // Store the messages int batches = 0; - for(Batch batch : b.getBatches()) { + for(Batch batch = b.getNextBatch(); batch != null; batch = b.getNextBatch()) { batches++; waitForPermissionToWrite(); contactLock.readLock().lock(); @@ -579,7 +586,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { try { Txn txn = db.startTransaction(); try { - lost = db.addReceivedBundle(txn, c, b.getId()); + lost = db.addReceivedBundle(txn, c, h.getId()); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java index cacf6cfe9262f987561a922318bea650e77ee57a..df94720028abbcd2c45083acdd5654abc5b54f97 100644 --- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java +++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java @@ -1,9 +1,10 @@ package net.sf.briar.db; +import java.io.IOException; +import java.security.SignatureException; import java.util.HashSet; import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,6 +20,8 @@ import net.sf.briar.api.protocol.BatchId; import net.sf.briar.api.protocol.Bundle; import net.sf.briar.api.protocol.BundleBuilder; import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.HeaderBuilder; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; @@ -48,8 +51,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { @Inject SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { - super(db, cleaner, batchBuilderProvider); + super(db, cleaner, headerBuilderProvider, batchBuilderProvider); } public void close() throws DbException { @@ -140,21 +144,20 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } public Bundle generateBundle(ContactId c, BundleBuilder b) - throws DbException { + throws DbException, IOException, SignatureException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c); - // Ack all batches received from c + HeaderBuilder h; + // Add acks synchronized(contactLock) { if(!containsContact(c)) throw new NoSuchContactException(); + h = headerBuilderProvider.get(); synchronized(messageStatusLock) { Txn txn = db.startTransaction(); try { - int numAcks = 0; - for(BatchId ack : db.removeBatchesToAck(txn, c)) { - b.addAck(ack); - numAcks++; - } + Set<BatchId> acks = db.removeBatchesToAck(txn, c); + h.addAcks(acks); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + numAcks + " acks"); + LOG.fine("Added " + acks.size() + " acks"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -162,19 +165,16 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } } - // Add a list of subscriptions + // Add subscriptions synchronized(contactLock) { if(!containsContact(c)) throw new NoSuchContactException(); synchronized(subscriptionLock) { Txn txn = db.startTransaction(); try { - int numSubs = 0; - for(GroupId g : db.getSubscriptions(txn)) { - b.addSubscription(g); - numSubs++; - } + Set<GroupId> subs = db.getSubscriptions(txn); + h.addSubscriptions(subs); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + numSubs + " subscriptions"); + LOG.fine("Added " + subs.size() + " subscriptions"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -188,14 +188,10 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { synchronized(transportLock) { Txn txn = db.startTransaction(); try { - int numTransports = 0; Map<String, String> transports = db.getTransports(txn); - for(Entry<String, String> e : transports.entrySet()) { - b.addTransport(e.getKey(), e.getValue()); - numTransports++; - } + h.addTransports(transports); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + numTransports + " transports"); + LOG.fine("Added " + transports.size() + " transports"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -203,8 +199,12 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } } - // Add as many messages as possible to the bundle + // Sign the header and add it to the bundle + Header header = h.build(); long capacity = b.getCapacity(); + capacity -= header.getSize(); + b.addHeader(header); + // Add as many messages as possible to the bundle while(true) { Batch batch = fillBatch(c, capacity); if(batch == null) break; // No more messages to send @@ -213,7 +213,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { capacity -= size; // If the batch is less than half full, stop trying - there may be // more messages trickling in but we can't wait forever - if(size * 2 < Batch.CAPACITY) break; + if(size * 2 < Batch.MAX_SIZE) break; } Bundle bundle = b.build(); if(LOG.isLoggable(Level.FINE)) @@ -222,14 +222,15 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { return bundle; } - private Batch fillBatch(ContactId c, long capacity) throws DbException { + private Batch fillBatch(ContactId c, long capacity) throws DbException, + SignatureException { synchronized(contactLock) { if(!containsContact(c)) throw new NoSuchContactException(); synchronized(messageLock) { synchronized(messageStatusLock) { Txn txn = db.startTransaction(); try { - capacity = Math.min(capacity, Batch.CAPACITY); + capacity = Math.min(capacity, Batch.MAX_SIZE); Iterator<MessageId> it = db.getSendableMessages(txn, c, capacity).iterator(); if(!it.hasNext()) { @@ -252,6 +253,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } catch(DbException e) { db.abortTransaction(txn); throw e; + } catch(SignatureException e) { + db.abortTransaction(txn); + throw e; } } } @@ -331,18 +335,20 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } - public void receiveBundle(ContactId c, Bundle b) throws DbException { + public void receiveBundle(ContactId c, Bundle b) throws DbException, + IOException, SignatureException { if(LOG.isLoggable(Level.FINE)) LOG.fine("Received bundle from " + c + ", " + b.getSize() + " bytes"); + Header h; // Mark all messages in acked batches as seen synchronized(contactLock) { if(!containsContact(c)) throw new NoSuchContactException(); + h = b.getHeader(); synchronized(messageLock) { synchronized(messageStatusLock) { - int acks = 0; - for(BatchId ack : b.getAcks()) { - acks++; + Set<BatchId> acks = h.getAcks(); + for(BatchId ack : acks) { Txn txn = db.startTransaction(); try { db.removeAckedBatch(txn, c, ack); @@ -353,7 +359,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + acks + " acks"); + LOG.fine("Received " + acks.size() + " acks"); } } } @@ -363,14 +369,12 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { synchronized(subscriptionLock) { Txn txn = db.startTransaction(); try { + // FIXME: Replace clearSubs and addSub with setSubs db.clearSubscriptions(txn, c); - int subs = 0; - for(GroupId g : b.getSubscriptions()) { - subs++; - db.addSubscription(txn, c, g); - } + Set<GroupId> subs = h.getSubscriptions(); + for(GroupId sub : subs) db.addSubscription(txn, c, sub); if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + subs + " subscriptions"); + LOG.fine("Received " + subs.size() + " subscriptions"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -384,7 +388,11 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { synchronized(transportLock) { Txn txn = db.startTransaction(); try { - db.setTransports(txn, c, b.getTransports()); + Map<String, String> transports = h.getTransports(); + db.setTransports(txn, c, transports); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Received " + transports.size() + + " transports"); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); @@ -394,7 +402,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } // Store the messages int batches = 0; - for(Batch batch : b.getBatches()) { + for(Batch batch = b.getNextBatch(); batch != null; batch = b.getNextBatch()) { batches++; waitForPermissionToWrite(); synchronized(contactLock) { @@ -436,7 +444,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { synchronized(messageStatusLock) { Txn txn = db.startTransaction(); try { - lost = db.addReceivedBundle(txn, c, b.getId()); + lost = db.addReceivedBundle(txn, c, h.getId()); db.commitTransaction(txn); } catch(DbException e) { db.abortTransaction(txn); diff --git a/components/net/sf/briar/protocol/BatchImpl.java b/components/net/sf/briar/protocol/BatchImpl.java index 68d0e12ddc1e7dc000d025be5dbee34e4b38ce91..97ccd5a71c8c0d70ffd3dbbd310cecddbf8cb86f 100644 --- a/components/net/sf/briar/protocol/BatchImpl.java +++ b/components/net/sf/briar/protocol/BatchImpl.java @@ -1,8 +1,6 @@ package net.sf.briar.protocol; -import java.util.ArrayList; import java.util.List; -import java.util.Random; import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.BatchId; @@ -11,15 +9,16 @@ import net.sf.briar.api.protocol.Message; /** A simple in-memory implementation of a batch. */ class BatchImpl implements Batch { - private final List<Message> messages = new ArrayList<Message>(); - private BatchId id = null; - private long size = 0L; + private final BatchId id; + private final long size; + private final List<Message> messages; + private final byte[] signature; - public void seal() { - // FIXME: Calculate batch ID - byte[] b = new byte[BatchId.LENGTH]; - new Random().nextBytes(b); - id = new BatchId(b); + BatchImpl(BatchId id, long size, List<Message> messages, byte[] signature) { + this.id = id; + this.size = size; + this.messages = messages; + this.signature = signature; } public BatchId getId() { @@ -34,8 +33,7 @@ class BatchImpl implements Batch { return messages; } - public void addMessage(Message m) { - messages.add(m); - size += m.getSize(); + public byte[] getSignature() { + return signature; } } diff --git a/components/net/sf/briar/protocol/BundleReader.java b/components/net/sf/briar/protocol/BundleReader.java new file mode 100644 index 0000000000000000000000000000000000000000..c3bcdf0ca52d6df14bf7e50a3c0634005f40161e --- /dev/null +++ b/components/net/sf/briar/protocol/BundleReader.java @@ -0,0 +1,94 @@ +package net.sf.briar.protocol; + +import java.io.IOException; +import java.security.SignatureException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sf.briar.api.protocol.Batch; +import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.Bundle; +import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.HeaderBuilder; +import net.sf.briar.api.protocol.Message; +import net.sf.briar.api.protocol.MessageParser; +import net.sf.briar.api.protocol.UniqueId; +import net.sf.briar.api.serial.FormatException; +import net.sf.briar.api.serial.Raw; +import net.sf.briar.api.serial.Reader; + +import com.google.inject.Provider; + +/** A bundle that deserialises its contents on demand using a reader. */ +abstract class BundleReader implements Bundle { + + private static enum State { START, FIRST_BATCH, MORE_BATCHES, END }; + + private final Reader r; + private final MessageParser messageParser; + private final Provider<HeaderBuilder> headerBuilderProvider; + private final Provider<BatchBuilder> batchBuilderProvider; + private State state = State.START; + + BundleReader(Reader r, MessageParser messageParser, + Provider<HeaderBuilder> headerBuilderProvider, + Provider<BatchBuilder> batchBuilderProvider) { + this.r = r; + this.messageParser = messageParser; + this.headerBuilderProvider = headerBuilderProvider; + this.batchBuilderProvider = batchBuilderProvider; + } + + public Header getHeader() throws IOException, SignatureException { + if(state != State.START) throw new IllegalStateException(); + r.setReadLimit(Header.MAX_SIZE); + Set<BatchId> acks = new HashSet<BatchId>(); + for(Raw raw : r.readList(Raw.class)) { + byte[] b = raw.getBytes(); + if(b.length != UniqueId.LENGTH) throw new FormatException(); + acks.add(new BatchId(b)); + } + Set<GroupId> subs = new HashSet<GroupId>(); + for(Raw raw : r.readList(Raw.class)) { + byte[] b = raw.getBytes(); + if(b.length != UniqueId.LENGTH) throw new FormatException(); + subs.add(new GroupId(b)); + } + Map<String, String> transports = r.readMap(String.class, String.class); + byte[] sig = r.readRaw(); + state = State.FIRST_BATCH; + HeaderBuilder h = headerBuilderProvider.get(); + h.addAcks(acks); + h.addSubscriptions(subs); + h.addTransports(transports); + h.setSignature(sig); + return h.build(); + } + + public Batch getNextBatch() throws IOException, SignatureException { + if(state == State.FIRST_BATCH) { + r.readListStart(); + state = State.MORE_BATCHES; + } + if(state != State.MORE_BATCHES) throw new IllegalStateException(); + if(r.hasListEnd()) { + r.readListEnd(); + state = State.END; + return null; + } + r.setReadLimit(Batch.MAX_SIZE); + List<Raw> messages = r.readList(Raw.class); + BatchBuilder b = batchBuilderProvider.get(); + for(Raw r : messages) { + Message m = messageParser.parseMessage(r.getBytes()); + b.addMessage(m); + } + byte[] sig = r.readRaw(); + b.setSignature(sig); + return b.build(); + } +} diff --git a/components/net/sf/briar/protocol/BundleWriter.java b/components/net/sf/briar/protocol/BundleWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..ccd5894752bc4c154de1a83fe336b03f402342d8 --- /dev/null +++ b/components/net/sf/briar/protocol/BundleWriter.java @@ -0,0 +1,66 @@ +package net.sf.briar.protocol; + +import java.io.IOException; + +import net.sf.briar.api.protocol.Batch; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.BundleBuilder; +import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.Message; +import net.sf.briar.api.serial.Writer; + +/** A bundle builder that serialises its contents using a writer. */ +abstract class BundleWriter implements BundleBuilder { + + private static enum State { START, FIRST_BATCH, MORE_BATCHES, END }; + + private final Writer w; + private final long capacity; + private State state = State.START; + + BundleWriter(Writer w, long capacity) { + this.w = w; + this.capacity = capacity; + } + + public long getCapacity() { + return capacity; + } + + public void addHeader(Header h) throws IOException { + if(state != State.START) throw new IllegalStateException(); + w.writeListStart(); + for(BatchId ack : h.getAcks()) w.writeRaw(ack); + w.writeListEnd(); + w.writeListStart(); + for(GroupId sub : h.getSubscriptions()) w.writeRaw(sub); + w.writeListEnd(); + w.writeMap(h.getTransports()); + w.writeRaw(h.getSignature()); + state = State.FIRST_BATCH; + } + + public void addBatch(Batch b) throws IOException { + if(state == State.FIRST_BATCH) { + w.writeListStart(); + state = State.MORE_BATCHES; + } + if(state != State.MORE_BATCHES) throw new IllegalStateException(); + w.writeListStart(); + for(Message m : b.getMessages()) w.writeRaw(m.getBody()); + w.writeListEnd(); + w.writeRaw(b.getSignature()); + } + + void close() throws IOException { + if(state == State.FIRST_BATCH) { + w.writeListStart(); + state = State.MORE_BATCHES; + } + if(state != State.MORE_BATCHES) throw new IllegalStateException(); + w.writeListEnd(); + w.close(); + state = State.END; + } +} diff --git a/components/net/sf/briar/protocol/FileBundle.java b/components/net/sf/briar/protocol/FileBundle.java new file mode 100644 index 0000000000000000000000000000000000000000..930f8699feff1a388e70c9ee62387d4ab36e2eda --- /dev/null +++ b/components/net/sf/briar/protocol/FileBundle.java @@ -0,0 +1,30 @@ +package net.sf.briar.protocol; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.HeaderBuilder; +import net.sf.briar.api.protocol.MessageParser; +import net.sf.briar.api.serial.ReaderFactory; + +import com.google.inject.Provider; + +class FileBundle extends BundleReader { + + private final File file; + + FileBundle(File file, ReaderFactory readerFactory, + MessageParser messageParser, + Provider<HeaderBuilder> headerBuilderProvider, + Provider<BatchBuilder> batchBuilderProvider) throws IOException { + super(readerFactory.createReader(new FileInputStream(file)), + messageParser, headerBuilderProvider, batchBuilderProvider); + this.file = file; + } + + public long getSize() throws IOException { + return file.length(); + } +} diff --git a/components/net/sf/briar/protocol/FileBundleBuilder.java b/components/net/sf/briar/protocol/FileBundleBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..085f637f74405c52296778929c85ec9d1d80b62a --- /dev/null +++ b/components/net/sf/briar/protocol/FileBundleBuilder.java @@ -0,0 +1,41 @@ +package net.sf.briar.protocol; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.Bundle; +import net.sf.briar.api.protocol.HeaderBuilder; +import net.sf.briar.api.protocol.MessageParser; +import net.sf.briar.api.serial.ReaderFactory; +import net.sf.briar.api.serial.WriterFactory; + +import com.google.inject.Provider; + +public class FileBundleBuilder extends BundleWriter { + + private final File file; + private final ReaderFactory readerFactory; + private final MessageParser messageParser; + private final Provider<HeaderBuilder> headerBuilderProvider; + private final Provider<BatchBuilder> batchBuilderProvider; + + FileBundleBuilder(File file, long capacity, WriterFactory writerFactory, + ReaderFactory readerFactory, MessageParser messageParser, + Provider<HeaderBuilder> headerBuilderProvider, + Provider<BatchBuilder> batchBuilderProvider) throws IOException { + super(writerFactory.createWriter(new FileOutputStream(file)), capacity); + this.file = file; + this.readerFactory = readerFactory; + this.messageParser = messageParser; + this.headerBuilderProvider = headerBuilderProvider; + this.batchBuilderProvider = batchBuilderProvider; + } + + public Bundle build() throws IOException { + super.close(); + return new FileBundle(file, readerFactory, messageParser, + headerBuilderProvider, batchBuilderProvider); + } +} diff --git a/components/net/sf/briar/protocol/HeaderImpl.java b/components/net/sf/briar/protocol/HeaderImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..763daf0f17e43f24bc4b2a0ec4b88edca2ba4efe --- /dev/null +++ b/components/net/sf/briar/protocol/HeaderImpl.java @@ -0,0 +1,55 @@ +package net.sf.briar.protocol; + +import java.util.Map; +import java.util.Set; + +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.BundleId; +import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; + +/** A simple in-memory implementation of a header. */ +class HeaderImpl implements Header { + + private final BundleId id; + private final long size; + private final Set<BatchId> acks; + private final Set<GroupId> subscriptions; + private final Map<String, String> transports; + private final byte[] signature; + + HeaderImpl(BundleId id, long size, Set<BatchId> acks, + Set<GroupId> subscriptions, Map<String, String> transports, + byte[] signature) { + this.id = id; + this.size = size; + this.acks = acks; + this.subscriptions = subscriptions; + this.transports = transports; + this.signature = signature; + } + + public BundleId getId() { + return id; + } + + public long getSize() { + return size; + } + + public Set<BatchId> getAcks() { + return acks; + } + + public Set<GroupId> getSubscriptions() { + return subscriptions; + } + + public Map<String, String> getTransports() { + return transports; + } + + public byte[] getSignature() { + return signature; + } +} diff --git a/components/net/sf/briar/protocol/MessageImpl.java b/components/net/sf/briar/protocol/MessageImpl.java index 0ae46a8221f77244cf2c3f602aed55f2cb5c3707..64306e4e44db716b1c46be54590257f47f5fa382 100644 --- a/components/net/sf/briar/protocol/MessageImpl.java +++ b/components/net/sf/briar/protocol/MessageImpl.java @@ -5,6 +5,7 @@ import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; +/** A simple in-memory implementation of a message. */ public class MessageImpl implements Message { private final MessageId id, parent; @@ -53,7 +54,7 @@ public class MessageImpl implements Message { @Override public boolean equals(Object o) { - return o instanceof MessageImpl && id.equals(((MessageImpl)o).id); + return o instanceof Message && id.equals(((Message)o).getId()); } @Override diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java index 30d41ee8280309124c2c9a283e7a2f81d62971b7..501ad51654e22e03357b11c6f3015cde3a1f34e8 100644 --- a/components/net/sf/briar/protocol/ProtocolModule.java +++ b/components/net/sf/briar/protocol/ProtocolModule.java @@ -1,10 +1,8 @@ package net.sf.briar.protocol; -import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Message; import com.google.inject.AbstractModule; -import com.google.inject.Provides; public class ProtocolModule extends AbstractModule { @@ -12,9 +10,4 @@ public class ProtocolModule extends AbstractModule { protected void configure() { bind(Message.class).to(MessageImpl.class); } - - @Provides - Batch createBatch() { - return new BatchImpl(); - } } diff --git a/test/build.xml b/test/build.xml index d908f95f32c4a5a40c135546de39f7c7f0f00ba6..f6a2653a303f9519d6b92a6cc978c938c23d4d14 100644 --- a/test/build.xml +++ b/test/build.xml @@ -20,6 +20,8 @@ <test name='net.sf.briar.i18n.FontManagerTest'/> <test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.invitation.InvitationWorkerTest'/> + <test name='net.sf.briar.protocol.BundleReaderTest'/> + <test name='net.sf.briar.protocol.BundleWriterTest'/> <test name='net.sf.briar.serial.ReaderImplTest'/> <test name='net.sf.briar.serial.WriterImplTest'/> <test name='net.sf.briar.setup.SetupWorkerTest'/> diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java index 5532d7a568b32177f350115fe1394a5b872f377a..ba3e7a4e08fc72c9ba66e652f2dde963f0cb51f8 100644 --- a/test/net/sf/briar/db/DatabaseComponentImplTest.java +++ b/test/net/sf/briar/db/DatabaseComponentImplTest.java @@ -7,6 +7,7 @@ import java.util.Collections; import net.sf.briar.api.db.DbException; import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.HeaderBuilder; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.db.DatabaseCleaner.Callback; @@ -24,6 +25,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { protected abstract <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( Database<T> database, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider); @Test @@ -33,14 +35,17 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); Callback db = createDatabaseComponentImpl(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.checkFreeSpaceAndClean(); @@ -54,8 +59,11 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -69,7 +77,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { will(returnValue(MIN_FREE_SPACE)); }}); Callback db = createDatabaseComponentImpl(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.checkFreeSpaceAndClean(); @@ -84,8 +92,11 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -101,7 +112,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { will(returnValue(MIN_FREE_SPACE)); }}); Callback db = createDatabaseComponentImpl(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.checkFreeSpaceAndClean(); @@ -116,8 +127,11 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -135,7 +149,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest { will(returnValue(MIN_FREE_SPACE)); }}); Callback db = createDatabaseComponentImpl(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.checkFreeSpaceAndClean(); diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index 0edcdc4be8b8a984d82739fb3b67f9013b697192..174c3a6afd64eb0a2ad2684ba4abbeb9d006b9e1 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -1,5 +1,7 @@ package net.sf.briar.db; +import java.io.IOException; +import java.security.SignatureException; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -20,6 +22,8 @@ import net.sf.briar.api.protocol.Bundle; import net.sf.briar.api.protocol.BundleBuilder; import net.sf.briar.api.protocol.BundleId; import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.HeaderBuilder; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.protocol.MessageImpl; @@ -32,8 +36,6 @@ import com.google.inject.Provider; public abstract class DatabaseComponentTest extends TestCase { - private static final int ONE_MEGABYTE = 1024 * 1024; - protected final Object txn = new Object(); protected final AuthorId authorId; protected final BatchId batchId; @@ -45,6 +47,11 @@ public abstract class DatabaseComponentTest extends TestCase { private final int size; private final byte[] body; private final Message message; + private final Set<ContactId> contacts; + private final Set<BatchId> acks; + private final Set<GroupId> subs; + private final Map<String, String> transports; + private final Set<MessageId> messages; public DatabaseComponentTest() { super(); @@ -60,26 +67,32 @@ public abstract class DatabaseComponentTest extends TestCase { body = new byte[size]; message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId, timestamp, body); + contacts = Collections.singleton(contactId); + acks = Collections.singleton(batchId); + subs = Collections.singleton(groupId); + transports = Collections.singletonMap("foo", "bar"); + messages = Collections.singleton(messageId); } protected abstract <T> DatabaseComponent createDatabaseComponent( Database<T> database, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider); @Test public void testSimpleCalls() throws DbException { - final Map<String, String> transports = - Collections.singletonMap("foo", "bar"); final Map<String, String> transports1 = Collections.singletonMap("foo", "bar baz"); - final Set<GroupId> subs = Collections.singleton(groupId); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -95,7 +108,7 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(contactId)); // getContacts() oneOf(database).getContacts(txn); - will(returnValue(Collections.singleton(contactId))); + will(returnValue(contacts)); // getTransports(contactId) oneOf(database).containsContact(txn, contactId); will(returnValue(true)); @@ -119,16 +132,16 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).close(); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.open(false); assertEquals(Rating.UNRATED, db.getRating(authorId)); assertEquals(contactId, db.addContact(transports)); - assertEquals(Collections.singleton(contactId), db.getContacts()); + assertEquals(contacts, db.getContacts()); assertEquals(transports, db.getTransports(contactId)); db.setTransports(contactId, transports1); db.subscribe(groupId); - assertEquals(Collections.singleton(groupId), db.getSubscriptions()); + assertEquals(subs, db.getSubscriptions()); db.unsubscribe(groupId); db.removeContact(contactId); db.close(); @@ -138,14 +151,16 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testNoParentStopsBackwardInclusion() throws DbException { - final Set<MessageId> messages = Collections.singleton(messageId); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // setRating(Rating.GOOD) allowing(database).startTransaction(); @@ -163,7 +178,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.setRating(authorId, Rating.GOOD); @@ -172,14 +187,16 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testMissingParentStopsBackwardInclusion() throws DbException { - final Set<MessageId> messages = Collections.singleton(messageId); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // setRating(Rating.GOOD) oneOf(database).startTransaction(); @@ -200,7 +217,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.setRating(authorId, Rating.GOOD); @@ -210,14 +227,16 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testChangingGroupsStopsBackwardInclusion() throws DbException { final GroupId groupId1 = new GroupId(TestUtils.getRandomId()); - final Set<MessageId> messages = Collections.singleton(messageId); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // setRating(Rating.GOOD) oneOf(database).startTransaction(); @@ -242,7 +261,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.setRating(authorId, Rating.GOOD); @@ -252,14 +271,16 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testUnaffectedParentStopsBackwardInclusion() throws DbException { - final Set<MessageId> messages = Collections.singleton(messageId); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // setRating(Rating.GOOD) oneOf(database).startTransaction(); @@ -287,7 +308,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.setRating(authorId, Rating.GOOD); @@ -297,14 +318,16 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testAffectedParentContinuesBackwardInclusion() throws DbException { - final Set<MessageId> messages = Collections.singleton(messageId); Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // setRating(Rating.GOOD) oneOf(database).startTransaction(); @@ -334,7 +357,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.setRating(authorId, Rating.GOOD); @@ -349,8 +372,11 @@ public abstract class DatabaseComponentTest extends TestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // addLocallyGeneratedMessage(message) oneOf(database).startTransaction(); @@ -360,7 +386,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.addLocallyGeneratedMessage(message); @@ -374,8 +400,11 @@ public abstract class DatabaseComponentTest extends TestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // addLocallyGeneratedMessage(message) oneOf(database).startTransaction(); @@ -387,7 +416,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.addLocallyGeneratedMessage(message); @@ -401,8 +430,11 @@ public abstract class DatabaseComponentTest extends TestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // addLocallyGeneratedMessage(message) oneOf(database).startTransaction(); @@ -412,7 +444,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).addMessage(txn, message); will(returnValue(true)); oneOf(database).getContacts(txn); - will(returnValue(Collections.singleton(contactId))); + will(returnValue(contacts)); oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); // The author is unrated and there are no sendable children oneOf(database).getRating(txn, authorId); @@ -423,7 +455,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.addLocallyGeneratedMessage(message); @@ -438,8 +470,11 @@ public abstract class DatabaseComponentTest extends TestCase { final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); context.checking(new Expectations() {{ // addLocallyGeneratedMessage(message) oneOf(database).startTransaction(); @@ -449,7 +484,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).addMessage(txn, message); will(returnValue(true)); oneOf(database).getContacts(txn); - will(returnValue(Collections.singleton(contactId))); + will(returnValue(contacts)); oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); // The author is rated GOOD and there are two sendable children oneOf(database).getRating(txn, authorId); @@ -463,7 +498,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.addLocallyGeneratedMessage(message); @@ -472,14 +507,17 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testGenerateBundleThrowsExceptionIfContactIsMissing() - throws DbException { + throws DbException, IOException, SignatureException { Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class); context.checking(new Expectations() {{ // Check that the contact is still in the DB @@ -490,7 +528,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); try { db.generateBundle(contactId, bundleBuilder); @@ -501,15 +539,22 @@ public abstract class DatabaseComponentTest extends TestCase { } @Test - public void testGenerateBundle() throws DbException { + public void testGenerateBundle() throws DbException, IOException, + SignatureException { + final long headerSize = 1234L; Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class); + final HeaderBuilder headerBuilder = context.mock(HeaderBuilder.class); + final Header header = context.mock(Header.class); final BatchBuilder batchBuilder = context.mock(BatchBuilder.class); final Batch batch = context.mock(Batch.class); final Bundle bundle = context.mock(Bundle.class); @@ -519,24 +564,33 @@ public abstract class DatabaseComponentTest extends TestCase { allowing(database).commitTransaction(txn); allowing(database).containsContact(txn, contactId); will(returnValue(true)); - // Add acks to the bundle + // Build the header + oneOf(headerBuilderProvider).get(); + will(returnValue(headerBuilder)); + // Add acks to the header oneOf(database).removeBatchesToAck(txn, contactId); - will(returnValue(Collections.singleton(batchId))); - oneOf(bundleBuilder).addAck(batchId); - // Add subscriptions to the bundle + will(returnValue(acks)); + oneOf(headerBuilder).addAcks(acks); + // Add subscriptions to the header oneOf(database).getSubscriptions(txn); - will(returnValue(Collections.singleton(groupId))); - oneOf(bundleBuilder).addSubscription(groupId); - // Add transports to the bundle + will(returnValue(subs)); + oneOf(headerBuilder).addSubscriptions(subs); + // Add transports to the header oneOf(database).getTransports(txn); - will(returnValue(Collections.singletonMap("foo", "bar"))); - oneOf(bundleBuilder).addTransport("foo", "bar"); - // Prepare to add batches to the bundle + will(returnValue(transports)); + oneOf(headerBuilder).addTransports(transports); + // Build the header + oneOf(headerBuilder).build(); + will(returnValue(header)); oneOf(bundleBuilder).getCapacity(); - will(returnValue((long) ONE_MEGABYTE)); - // Add messages to the batch - oneOf(database).getSendableMessages(txn, contactId, Batch.CAPACITY); - will(returnValue(Collections.singleton(messageId))); + will(returnValue(1024L * 1024L)); + oneOf(header).getSize(); + will(returnValue(headerSize)); + oneOf(bundleBuilder).addHeader(header); + // Add a batch to the bundle + oneOf(database).getSendableMessages(txn, contactId, + Batch.MAX_SIZE - headerSize); + will(returnValue(messages)); oneOf(batchBuilderProvider).get(); will(returnValue(batchBuilder)); oneOf(database).getMessage(txn, messageId); @@ -547,8 +601,8 @@ public abstract class DatabaseComponentTest extends TestCase { // Record the batch as outstanding oneOf(batch).getId(); will(returnValue(batchId)); - oneOf(database).addOutstandingBatch(txn, contactId, batchId, - Collections.singleton(messageId)); + oneOf(database).addOutstandingBatch( + txn, contactId, batchId, messages); // Add the batch to the bundle oneOf(bundleBuilder).addBatch(batch); // Check whether to add another batch @@ -559,7 +613,7 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(bundle)); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.generateBundle(contactId, bundleBuilder); @@ -568,14 +622,17 @@ public abstract class DatabaseComponentTest extends TestCase { @Test public void testReceiveBundleThrowsExceptionIfContactIsMissing() - throws DbException { + throws DbException, IOException, SignatureException { Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); final Bundle bundle = context.mock(Bundle.class); context.checking(new Expectations() {{ // Check that the contact is still in the DB @@ -586,7 +643,7 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); try { db.receiveBundle(contactId, bundle); @@ -597,17 +654,20 @@ public abstract class DatabaseComponentTest extends TestCase { } @Test - public void testReceivedBundle() throws DbException { - final Map<String, String> transports = - Collections.singletonMap("foo", "bar"); + public void testReceivedBundle() throws DbException, IOException, + SignatureException { Mockery context = new Mockery(); @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); @SuppressWarnings("unchecked") - final Provider<BatchBuilder> batchBuilderProvider = + final Provider<HeaderBuilder> headerBuilderProvider = context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); final Bundle bundle = context.mock(Bundle.class); + final Header header = context.mock(Header.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -615,22 +675,25 @@ public abstract class DatabaseComponentTest extends TestCase { allowing(database).commitTransaction(txn); allowing(database).containsContact(txn, contactId); will(returnValue(true)); + // Header + oneOf(bundle).getHeader(); + will(returnValue(header)); // Acks - oneOf(bundle).getAcks(); - will(returnValue(Collections.singleton(batchId))); + oneOf(header).getAcks(); + will(returnValue(acks)); oneOf(database).removeAckedBatch(txn, contactId, batchId); // Subscriptions oneOf(database).clearSubscriptions(txn, contactId); - oneOf(bundle).getSubscriptions(); - will(returnValue(Collections.singleton(groupId))); + oneOf(header).getSubscriptions(); + will(returnValue(subs)); oneOf(database).addSubscription(txn, contactId, groupId); // Transports - oneOf(bundle).getTransports(); + oneOf(header).getTransports(); will(returnValue(transports)); oneOf(database).setTransports(txn, contactId, transports); // Batches - oneOf(bundle).getBatches(); - will(returnValue(Collections.singleton(batch))); + oneOf(bundle).getNextBatch(); + will(returnValue(batch)); oneOf(batch).getMessages(); will(returnValue(Collections.singleton(message))); oneOf(database).containsSubscription(txn, groupId); @@ -642,15 +705,18 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(batch).getId(); will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); + // Any more batches? Nope + oneOf(bundle).getNextBatch(); + will(returnValue(null)); // Lost batches - oneOf(bundle).getId(); + oneOf(header).getId(); will(returnValue(bundleId)); oneOf(database).addReceivedBundle(txn, contactId, bundleId); will(returnValue(Collections.singleton(batchId))); oneOf(database).removeLostBatch(txn, contactId, batchId); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); db.receiveBundle(contactId, bundle); diff --git a/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java b/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java index 77e32e60f4c75ff0a65fc5f93a8a7e5dd52bb1ed..ccc1a714d3f4064b2514c3f1b8d659a9243238b6 100644 --- a/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java +++ b/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java @@ -2,6 +2,7 @@ package net.sf.briar.db; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.HeaderBuilder; import com.google.inject.Provider; @@ -11,16 +12,18 @@ extends DatabaseComponentImplTest { @Override protected <T> DatabaseComponent createDatabaseComponent( Database<T> database, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { return createDatabaseComponentImpl(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); } @Override protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( Database<T> database, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { return new ReadWriteLockDatabaseComponent<T>(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); } } diff --git a/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java b/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java index e562db8ba9c1525f363f6626105d43c93785d1ef..5c8f576c3d849b5aeae43d770cabfe266c2f6954 100644 --- a/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java +++ b/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java @@ -2,6 +2,7 @@ package net.sf.briar.db; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.HeaderBuilder; import com.google.inject.Provider; @@ -11,16 +12,18 @@ extends DatabaseComponentImplTest { @Override protected <T> DatabaseComponent createDatabaseComponent( Database<T> database, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { return createDatabaseComponentImpl(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); } @Override protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( Database<T> database, DatabaseCleaner cleaner, + Provider<HeaderBuilder> headerBuilderProvider, Provider<BatchBuilder> batchBuilderProvider) { return new SynchronizedDatabaseComponent<T>(database, cleaner, - batchBuilderProvider); + headerBuilderProvider, batchBuilderProvider); } } diff --git a/test/net/sf/briar/protocol/BundleReaderTest.java b/test/net/sf/briar/protocol/BundleReaderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b0a5f5d012da9b47b43e82fdfef23e0e7cba56ab --- /dev/null +++ b/test/net/sf/briar/protocol/BundleReaderTest.java @@ -0,0 +1,251 @@ +package net.sf.briar.protocol; + +import java.io.IOException; +import java.security.SignatureException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import junit.framework.TestCase; +import net.sf.briar.TestUtils; +import net.sf.briar.api.protocol.Batch; +import net.sf.briar.api.protocol.BatchBuilder; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.HeaderBuilder; +import net.sf.briar.api.protocol.Message; +import net.sf.briar.api.protocol.MessageParser; +import net.sf.briar.api.serial.Raw; +import net.sf.briar.api.serial.Reader; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Test; + +import com.google.inject.Provider; + +public class BundleReaderTest extends TestCase { + + private final long size = 1024L * 1024L; + private final BatchId ack = new BatchId(TestUtils.getRandomId()); + private final List<Raw> rawAcks = + Collections.<Raw>singletonList(new TestRaw(ack.getBytes())); + private final Set<BatchId> acks = Collections.singleton(ack); + private final GroupId sub = new GroupId(TestUtils.getRandomId()); + private final List<Raw> rawSubs = + Collections.<Raw>singletonList(new TestRaw(sub.getBytes())); + private final Set<GroupId> subs = Collections.singleton(sub); + private final Map<String, String> transports = + Collections.singletonMap("foo", "bar"); + private final byte[] headerSig = TestUtils.getRandomId(); + private final byte[] messageBody = new byte[123]; + private final List<Raw> rawMessages = + Collections.<Raw>singletonList(new TestRaw(messageBody)); + private final byte[] batchSig = TestUtils.getRandomId(); + + @Test + public void testGetHeader() throws IOException, SignatureException { + Mockery context = new Mockery(); + final Reader reader = context.mock(Reader.class); + final MessageParser messageParser = context.mock(MessageParser.class); + @SuppressWarnings("unchecked") + final Provider<HeaderBuilder> headerBuilderProvider = + context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); + final HeaderBuilder headerBuilder = context.mock(HeaderBuilder.class); + final Header header = context.mock(Header.class); + context.checking(new Expectations() {{ + oneOf(reader).setReadLimit(Header.MAX_SIZE); + oneOf(headerBuilderProvider).get(); + will(returnValue(headerBuilder)); + // Acks + oneOf(reader).readList(Raw.class); + will(returnValue(rawAcks)); + oneOf(headerBuilder).addAcks(acks); + // Subs + oneOf(reader).readList(Raw.class); + will(returnValue(rawSubs)); + oneOf(headerBuilder).addSubscriptions(subs); + // Transports + oneOf(reader).readMap(String.class, String.class); + will(returnValue(transports)); + oneOf(headerBuilder).addTransports(transports); + // Signature + oneOf(reader).readRaw(); + will(returnValue(headerSig)); + oneOf(headerBuilder).setSignature(headerSig); + // Build the header + oneOf(headerBuilder).build(); + will(returnValue(header)); + }}); + BundleReader r = createBundleReader(reader, messageParser, + headerBuilderProvider, batchBuilderProvider); + + assertEquals(header, r.getHeader()); + + context.assertIsSatisfied(); + } + + @Test + public void testBatchBeforeHeaderThrowsException() throws IOException, + SignatureException { + Mockery context = new Mockery(); + final Reader reader = context.mock(Reader.class); + final MessageParser messageParser = context.mock(MessageParser.class); + @SuppressWarnings("unchecked") + final Provider<HeaderBuilder> headerBuilderProvider = + context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); + BundleReader r = createBundleReader(reader, messageParser, + headerBuilderProvider, batchBuilderProvider); + + try { + r.getNextBatch(); + assertTrue(false); + } catch(IllegalStateException expected) {} + + context.assertIsSatisfied(); + } + + @Test + public void testGetHeaderNoBatches() throws IOException, + SignatureException { + Mockery context = new Mockery(); + final Reader reader = context.mock(Reader.class); + final MessageParser messageParser = context.mock(MessageParser.class); + @SuppressWarnings("unchecked") + final Provider<HeaderBuilder> headerBuilderProvider = + context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); + final HeaderBuilder headerBuilder = context.mock(HeaderBuilder.class); + final Header header = context.mock(Header.class); + context.checking(new Expectations() {{ + oneOf(reader).setReadLimit(Header.MAX_SIZE); + oneOf(headerBuilderProvider).get(); + will(returnValue(headerBuilder)); + // Acks + oneOf(reader).readList(Raw.class); + will(returnValue(rawAcks)); + oneOf(headerBuilder).addAcks(acks); + // Subs + oneOf(reader).readList(Raw.class); + will(returnValue(rawSubs)); + oneOf(headerBuilder).addSubscriptions(subs); + // Transports + oneOf(reader).readMap(String.class, String.class); + will(returnValue(transports)); + oneOf(headerBuilder).addTransports(transports); + // Signature + oneOf(reader).readRaw(); + will(returnValue(headerSig)); + oneOf(headerBuilder).setSignature(headerSig); + // Build the header + oneOf(headerBuilder).build(); + will(returnValue(header)); + // No batches + oneOf(reader).readListStart(); + oneOf(reader).hasListEnd(); + will(returnValue(true)); + oneOf(reader).readListEnd(); + }}); + BundleReader r = createBundleReader(reader, messageParser, + headerBuilderProvider, batchBuilderProvider); + + assertEquals(header, r.getHeader()); + assertNull(r.getNextBatch()); + + context.assertIsSatisfied(); + } + + @Test + public void testGetHeaderOneBatch() throws IOException, + SignatureException { + Mockery context = new Mockery(); + final Reader reader = context.mock(Reader.class); + final MessageParser messageParser = context.mock(MessageParser.class); + @SuppressWarnings("unchecked") + final Provider<HeaderBuilder> headerBuilderProvider = + context.mock(Provider.class); + @SuppressWarnings("unchecked") + final Provider<BatchBuilder> batchBuilderProvider = + context.mock(Provider.class, "batchBuilderProvider"); + final HeaderBuilder headerBuilder = context.mock(HeaderBuilder.class); + final Header header = context.mock(Header.class); + final BatchBuilder batchBuilder = context.mock(BatchBuilder.class); + final Batch batch = context.mock(Batch.class); + final Message message = context.mock(Message.class); + context.checking(new Expectations() {{ + oneOf(reader).setReadLimit(Header.MAX_SIZE); + oneOf(headerBuilderProvider).get(); + will(returnValue(headerBuilder)); + // Acks + oneOf(reader).readList(Raw.class); + will(returnValue(rawAcks)); + oneOf(headerBuilder).addAcks(acks); + // Subs + oneOf(reader).readList(Raw.class); + will(returnValue(rawSubs)); + oneOf(headerBuilder).addSubscriptions(subs); + // Transports + oneOf(reader).readMap(String.class, String.class); + will(returnValue(transports)); + oneOf(headerBuilder).addTransports(transports); + // Signature + oneOf(reader).readRaw(); + will(returnValue(headerSig)); + oneOf(headerBuilder).setSignature(headerSig); + // Build the header + oneOf(headerBuilder).build(); + will(returnValue(header)); + // First batch + oneOf(reader).readListStart(); + oneOf(reader).hasListEnd(); + will(returnValue(false)); + oneOf(reader).setReadLimit(Batch.MAX_SIZE); + oneOf(batchBuilderProvider).get(); + will(returnValue(batchBuilder)); + oneOf(reader).readList(Raw.class); + will(returnValue(rawMessages)); + oneOf(messageParser).parseMessage(messageBody); + will(returnValue(message)); + oneOf(batchBuilder).addMessage(message); + oneOf(reader).readRaw(); + will(returnValue(batchSig)); + oneOf(batchBuilder).setSignature(batchSig); + oneOf(batchBuilder).build(); + will(returnValue(batch)); + // No more batches + oneOf(reader).hasListEnd(); + will(returnValue(true)); + oneOf(reader).readListEnd(); + }}); + BundleReader r = createBundleReader(reader, messageParser, + headerBuilderProvider, batchBuilderProvider); + + assertEquals(header, r.getHeader()); + assertEquals(batch, r.getNextBatch()); + assertNull(r.getNextBatch()); + + context.assertIsSatisfied(); + } + + private BundleReader createBundleReader(Reader reader, + MessageParser messageParser, + Provider<HeaderBuilder> headerBuilderProvider, + Provider<BatchBuilder> batchBuilderProvider) { + return new BundleReader(reader, messageParser, headerBuilderProvider, + batchBuilderProvider) { + public long getSize() { + return size; + } + }; + } +} diff --git a/test/net/sf/briar/protocol/BundleWriterTest.java b/test/net/sf/briar/protocol/BundleWriterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a2e025300321a2af3e11ff4548b84f5431de6d6b --- /dev/null +++ b/test/net/sf/briar/protocol/BundleWriterTest.java @@ -0,0 +1,242 @@ +package net.sf.briar.protocol; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import junit.framework.TestCase; +import net.sf.briar.TestUtils; +import net.sf.briar.api.protocol.Batch; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.Bundle; +import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Header; +import net.sf.briar.api.protocol.Message; +import net.sf.briar.api.serial.Writer; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.Test; + +public class BundleWriterTest extends TestCase { + + private final BatchId ack = new BatchId(TestUtils.getRandomId()); + private final Set<BatchId> acks = Collections.singleton(ack); + private final GroupId sub = new GroupId(TestUtils.getRandomId()); + private final Set<GroupId> subs = Collections.singleton(sub); + private final Map<String, String> transports = + Collections.singletonMap("foo", "bar"); + private final byte[] headerSig = TestUtils.getRandomId(); + private final long capacity = 1024L * 1024L; + private final byte[] messageBody = new byte[123]; + private final byte[] batchSig = TestUtils.getRandomId(); + + @Test + public void testAddHeader() throws IOException { + Mockery context = new Mockery(); + final Writer writer = context.mock(Writer.class); + final Header header = context.mock(Header.class); + context.checking(new Expectations() {{ + // Acks + oneOf(writer).writeListStart(); + oneOf(header).getAcks(); + will(returnValue(acks)); + oneOf(writer).writeRaw(ack); + oneOf(writer).writeListEnd(); + // Subs + oneOf(writer).writeListStart(); + oneOf(header).getSubscriptions(); + will(returnValue(subs)); + oneOf(writer).writeRaw(sub); + oneOf(writer).writeListEnd(); + // Transports + oneOf(header).getTransports(); + will(returnValue(transports)); + oneOf(writer).writeMap(transports); + // Signature + oneOf(header).getSignature(); + will(returnValue(headerSig)); + oneOf(writer).writeRaw(headerSig); + }}); + BundleWriter w = createBundleWriter(writer); + + w.addHeader(header); + + context.assertIsSatisfied(); + } + + @Test + public void testAddHeaderEmptyLists() throws IOException { + Mockery context = new Mockery(); + final Writer writer = context.mock(Writer.class); + final Header header = context.mock(Header.class); + context.checking(new Expectations() {{ + // Acks + oneOf(writer).writeListStart(); + oneOf(header).getAcks(); + will(returnValue(Collections.emptySet())); + oneOf(writer).writeListEnd(); + // Subs + oneOf(writer).writeListStart(); + oneOf(header).getSubscriptions(); + will(returnValue(Collections.emptySet())); + oneOf(writer).writeListEnd(); + // Transports + oneOf(header).getTransports(); + will(returnValue(Collections.emptyMap())); + oneOf(writer).writeMap(Collections.emptyMap()); + // Signature + oneOf(header).getSignature(); + will(returnValue(headerSig)); + oneOf(writer).writeRaw(headerSig); + }}); + BundleWriter w = createBundleWriter(writer); + + w.addHeader(header); + + context.assertIsSatisfied(); + } + + @Test + public void testBatchBeforeHeaderThrowsException() throws IOException { + Mockery context = new Mockery(); + final Writer writer = context.mock(Writer.class); + final Batch batch = context.mock(Batch.class); + BundleWriter w = createBundleWriter(writer); + + try { + w.addBatch(batch); + assertTrue(false); + } catch(IllegalStateException expected) {} + + context.assertIsSatisfied(); + } + + @Test + public void testCloseBeforeHeaderThrowsException() throws IOException { + Mockery context = new Mockery(); + final Writer writer = context.mock(Writer.class); + BundleWriter w = createBundleWriter(writer); + + try { + w.close(); + assertTrue(false); + } catch(IllegalStateException expected) {} + + context.assertIsSatisfied(); + } + + @Test + public void testCloseWithoutBatchesDoesNotThrowException() + throws IOException { + Mockery context = new Mockery(); + final Writer writer = context.mock(Writer.class); + final Header header = context.mock(Header.class); + context.checking(new Expectations() {{ + // Acks + oneOf(writer).writeListStart(); + oneOf(header).getAcks(); + will(returnValue(acks)); + oneOf(writer).writeRaw(ack); + oneOf(writer).writeListEnd(); + // Subs + oneOf(writer).writeListStart(); + oneOf(header).getSubscriptions(); + will(returnValue(subs)); + oneOf(writer).writeRaw(sub); + oneOf(writer).writeListEnd(); + // Transports + oneOf(header).getTransports(); + will(returnValue(transports)); + oneOf(writer).writeMap(transports); + // Signature + oneOf(header).getSignature(); + will(returnValue(headerSig)); + oneOf(writer).writeRaw(headerSig); + // Close - write an empty list of batches + oneOf(writer).writeListStart(); + oneOf(writer).writeListEnd(); + oneOf(writer).close(); + }}); + BundleWriter w = createBundleWriter(writer); + + w.addHeader(header); + w.close(); + + context.assertIsSatisfied(); + } + + @Test + public void testAddHeaderAndTwoBatches() throws IOException { + Mockery context = new Mockery(); + final Writer writer = context.mock(Writer.class); + final Header header = context.mock(Header.class); + final Batch batch = context.mock(Batch.class); + final Message message = context.mock(Message.class); + context.checking(new Expectations() {{ + // Acks + oneOf(writer).writeListStart(); + oneOf(header).getAcks(); + will(returnValue(acks)); + oneOf(writer).writeRaw(ack); + oneOf(writer).writeListEnd(); + // Subs + oneOf(writer).writeListStart(); + oneOf(header).getSubscriptions(); + will(returnValue(subs)); + oneOf(writer).writeRaw(sub); + oneOf(writer).writeListEnd(); + // Transports + oneOf(header).getTransports(); + will(returnValue(transports)); + oneOf(writer).writeMap(transports); + // Signature + oneOf(header).getSignature(); + will(returnValue(headerSig)); + oneOf(writer).writeRaw(headerSig); + // First batch + oneOf(writer).writeListStart(); + oneOf(writer).writeListStart(); + oneOf(batch).getMessages(); + will(returnValue(Collections.singleton(message))); + oneOf(message).getBody(); + will(returnValue(messageBody)); + oneOf(writer).writeRaw(messageBody); + oneOf(writer).writeListEnd(); + oneOf(batch).getSignature(); + will(returnValue(batchSig)); + oneOf(writer).writeRaw(batchSig); + // Second batch + oneOf(writer).writeListStart(); + oneOf(batch).getMessages(); + will(returnValue(Collections.singleton(message))); + oneOf(message).getBody(); + will(returnValue(messageBody)); + oneOf(writer).writeRaw(messageBody); + oneOf(writer).writeListEnd(); + oneOf(batch).getSignature(); + will(returnValue(batchSig)); + oneOf(writer).writeRaw(batchSig); + // Close + oneOf(writer).writeListEnd(); + oneOf(writer).close(); + }}); + BundleWriter w = createBundleWriter(writer); + + w.addHeader(header); + w.addBatch(batch); + w.addBatch(batch); + w.close(); + + context.assertIsSatisfied(); + } + + private BundleWriter createBundleWriter(Writer writer) { + return new BundleWriter(writer, capacity) { + public Bundle build() throws IOException { + return null; + } + }; + } +} diff --git a/test/net/sf/briar/protocol/TestRaw.java b/test/net/sf/briar/protocol/TestRaw.java new file mode 100644 index 0000000000000000000000000000000000000000..f9f685b3632bfaca3244040a4c2c7d05cdcbefa3 --- /dev/null +++ b/test/net/sf/briar/protocol/TestRaw.java @@ -0,0 +1,29 @@ +package net.sf.briar.protocol; + +import java.util.Arrays; + +import net.sf.briar.api.serial.Raw; + +class TestRaw implements Raw { + + private final byte[] bytes; + + TestRaw(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] getBytes() { + return bytes; + } + + @Override + public int hashCode() { + return Arrays.hashCode(bytes); + } + + @Override + public boolean equals(Object o) { + if(o instanceof Raw) return Arrays.equals(bytes, ((Raw) o).getBytes()); + return false; + } +}