Protocol refactoring. Each bundle now consists of a signed header and zero or more signed batches. There is no overall signature on the bundle, since the bundle's contents may need to be processed before the entire bundle has been read. The protocol does not prevent an adversary from removing batches from a bundle, reordering batches, moving them from one bundle to another, etc. However, since each batch is signed and acknowledged independently, no such guarantees are required. Bundle IDs will go away when the retransmission mechanism is changed.

This commit is contained in:
akwizgran
2011-07-12 12:55:46 +01:00
parent 4977695a79
commit e0509db45d
28 changed files with 1198 additions and 228 deletions

View File

@@ -1,5 +1,7 @@
package net.sf.briar.api.db; package net.sf.briar.api.db;
import java.io.IOException;
import java.security.SignatureException;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -49,7 +51,7 @@ public interface DatabaseComponent {
* Generates a bundle of acknowledgements, subscriptions, and batches of * Generates a bundle of acknowledgements, subscriptions, and batches of
* messages for the given contact. * 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. */ /** Returns the IDs of all contacts. */
Set<ContactId> getContacts() throws DbException; Set<ContactId> getContacts() throws DbException;
@@ -71,7 +73,7 @@ public interface DatabaseComponent {
* messages received from the given contact. Some or all of the messages * messages received from the given contact. Some or all of the messages
* in the bundle may be stored. * 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. */ /** Removes a contact (and all associated state) from the database. */
void removeContact(ContactId c) throws DbException; void removeContact(ContactId c) throws DbException;

View File

@@ -1,16 +1,19 @@
package net.sf.briar.api.protocol; 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 interface Batch {
public static final long CAPACITY = 1024L * 1024L; public static final int MAX_SIZE = 1024 * 1024;
/** Returns the batch's unique identifier. */ /** Returns the batch's unique identifier. */
BatchId getId(); BatchId getId();
/** Returns the size of the batch in bytes. */ /** Returns the size of the serialised batch in bytes. */
long getSize(); long getSize();
/** Returns the messages contained in the batch. */ /** Returns the messages contained in the batch. */
Iterable<Message> getMessages(); Iterable<Message> getMessages();
/** Returns the sender's signature over the contents of the batch. */
byte[] getSignature();
} }

View File

@@ -1,10 +1,15 @@
package net.sf.briar.api.protocol; package net.sf.briar.api.protocol;
import java.security.SignatureException;
public interface BatchBuilder { public interface BatchBuilder {
/** Adds a message to the batch. */ /** Adds a message to the batch. */
void addMessage(Message m); void addMessage(Message m);
/** Sets the sender's signature over the contents of the batch. */
void setSignature(byte[] sig);
/** Builds and returns the batch. */ /** Builds and returns the batch. */
Batch build(); Batch build() throws SignatureException;
} }

View File

@@ -1,28 +1,21 @@
package net.sf.briar.api.protocol; 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 { public interface Bundle {
/** Returns the bundle's unique identifier. */ /** Returns the size of the serialised bundle in bytes. */
BundleId getId(); long getSize() throws IOException;
/** Returns the bundle's capacity in bytes. */ /** Returns the bundle's header. */
long getCapacity(); Header getHeader() throws IOException, SignatureException;
/** Returns the bundle's size in bytes. */ /**
long getSize(); * Returns the next batch of messages, or null if there are no more batches.
*/
/** Returns the acknowledgements contained in the bundle. */ Batch getNextBatch() throws IOException, SignatureException;
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();
} }

View File

@@ -1,22 +1,18 @@
package net.sf.briar.api.protocol; package net.sf.briar.api.protocol;
import java.io.IOException;
public interface BundleBuilder { public interface BundleBuilder {
/** Returns the bundle's capacity in bytes. */ /** Returns the bundle's capacity in bytes. */
long getCapacity(); long getCapacity() throws IOException;
/** Adds an acknowledgement to the bundle. */ /** Adds a header to the bundle. */
void addAck(BatchId b); void addHeader(Header h) throws IOException;
/** Adds a subscription to the bundle. */
void addSubscription(GroupId g);
/** Adds a transport detail to the bundle. */
void addTransport(String key, String value);
/** Adds a batch of messages to the bundle. */ /** Adds a batch of messages to the bundle. */
void addBatch(Batch b); void addBatch(Batch b) throws IOException;
/** Builds and returns the bundle. */ /** Builds and returns the bundle. */
Bundle build(); Bundle build() throws IOException;
} }

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -2,7 +2,9 @@ package net.sf.briar.api.protocol;
import java.util.Arrays; 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; public static final int LENGTH = 32;

View File

@@ -10,6 +10,7 @@ import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.BatchBuilder; 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.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
@@ -27,6 +28,7 @@ DatabaseCleaner.Callback {
protected final Database<Txn> db; protected final Database<Txn> db;
protected final DatabaseCleaner cleaner; protected final DatabaseCleaner cleaner;
protected final Provider<HeaderBuilder> headerBuilderProvider;
protected final Provider<BatchBuilder> batchBuilderProvider; protected final Provider<BatchBuilder> batchBuilderProvider;
private final Object spaceLock = new Object(); private final Object spaceLock = new Object();
@@ -36,9 +38,11 @@ DatabaseCleaner.Callback {
private volatile boolean writesAllowed = true; private volatile boolean writesAllowed = true;
DatabaseComponentImpl(Database<Txn> db, DatabaseCleaner cleaner, DatabaseComponentImpl(Database<Txn> db, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
this.db = db; this.db = db;
this.cleaner = cleaner; this.cleaner = cleaner;
this.headerBuilderProvider = headerBuilderProvider;
this.batchBuilderProvider = batchBuilderProvider; this.batchBuilderProvider = batchBuilderProvider;
} }

View File

@@ -1,9 +1,10 @@
package net.sf.briar.db; package net.sf.briar.db;
import java.io.IOException;
import java.security.SignatureException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level; 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.Bundle;
import net.sf.briar.api.protocol.BundleBuilder; import net.sf.briar.api.protocol.BundleBuilder;
import net.sf.briar.api.protocol.GroupId; 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.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
@@ -55,8 +58,9 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
@Inject @Inject
ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner, ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
super(db, cleaner, batchBuilderProvider); super(db, cleaner, headerBuilderProvider, batchBuilderProvider);
} }
public void close() throws DbException { public void close() throws DbException {
@@ -187,23 +191,22 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
public Bundle generateBundle(ContactId c, BundleBuilder b) public Bundle generateBundle(ContactId c, BundleBuilder b)
throws DbException { throws DbException, IOException, SignatureException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c); if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
// Ack all batches received from c HeaderBuilder h;
// Add acks
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
h = headerBuilderProvider.get();
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
int numAcks = 0; Set<BatchId> acks = db.removeBatchesToAck(txn, c);
for(BatchId ack : db.removeBatchesToAck(txn, c)) { h.addAcks(acks);
b.addAck(ack);
numAcks++;
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + numAcks + " acks"); LOG.fine("Added " + acks.size() + " acks");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -215,7 +218,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} finally { } finally {
contactLock.readLock().unlock(); contactLock.readLock().unlock();
} }
// Add a list of subscriptions // Add subscriptions
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
@@ -223,13 +226,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
int numSubs = 0; Set<GroupId> subs = db.getSubscriptions(txn);
for(GroupId g : db.getSubscriptions(txn)) { h.addSubscriptions(subs);
b.addSubscription(g);
numSubs++;
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + numSubs + " subscriptions"); LOG.fine("Added " + subs.size() + " subscriptions");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -249,14 +249,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
int numTransports = 0;
Map<String, String> transports = db.getTransports(txn); Map<String, String> transports = db.getTransports(txn);
for(Entry<String, String> e : transports.entrySet()) { h.addTransports(transports);
b.addTransport(e.getKey(), e.getValue());
numTransports++;
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + numTransports + " transports"); LOG.fine("Added " + transports.size() + " transports");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -268,8 +264,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} finally { } finally {
contactLock.readLock().unlock(); 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(); long capacity = b.getCapacity();
capacity -= header.getSize();
b.addHeader(header);
// Add as many messages as possible to the bundle
while(true) { while(true) {
Batch batch = fillBatch(c, capacity); Batch batch = fillBatch(c, capacity);
if(batch == null) break; // No more messages to send if(batch == null) break; // No more messages to send
@@ -278,7 +278,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
capacity -= size; capacity -= size;
// If the batch is less than half full, stop trying - there may be // If the batch is less than half full, stop trying - there may be
// more messages trickling in but we can't wait forever // 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(); Bundle bundle = b.build();
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
@@ -287,20 +287,20 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
return bundle; return bundle;
} }
private Batch fillBatch(ContactId c, long capacity) throws DbException { private Batch fillBatch(ContactId c, long capacity) throws DbException,
SignatureException {
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
messageLock.readLock().lock(); messageLock.readLock().lock();
try { try {
Set<MessageId> sent; Set<MessageId> sent;
BatchBuilder b;
Batch batch; Batch batch;
messageStatusLock.readLock().lock(); messageStatusLock.readLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
capacity = Math.min(capacity, Batch.CAPACITY); capacity = Math.min(capacity, Batch.MAX_SIZE);
Iterator<MessageId> it = Iterator<MessageId> it =
db.getSendableMessages(txn, c, capacity).iterator(); db.getSendableMessages(txn, c, capacity).iterator();
if(!it.hasNext()) { if(!it.hasNext()) {
@@ -308,7 +308,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
return null; // No more messages to send return null; // No more messages to send
} }
sent = new HashSet<MessageId>(); sent = new HashSet<MessageId>();
b = batchBuilderProvider.get(); BatchBuilder b = batchBuilderProvider.get();
while(it.hasNext()) { while(it.hasNext()) {
MessageId m = it.next(); MessageId m = it.next();
b.addMessage(db.getMessage(txn, m)); b.addMessage(db.getMessage(txn, m));
@@ -319,6 +319,9 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
throw e; throw e;
} catch(SignatureException e) {
db.abortTransaction(txn);
throw e;
} }
} finally { } finally {
messageStatusLock.readLock().unlock(); 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)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received bundle from " + c + ", " LOG.fine("Received bundle from " + c + ", "
+ b.getSize() + " bytes"); + b.getSize() + " bytes");
Header h;
// Mark all messages in acked batches as seen // Mark all messages in acked batches as seen
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
h = b.getHeader();
messageLock.readLock().lock(); messageLock.readLock().lock();
try { try {
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
try { try {
int acks = 0; Set<BatchId> acks = h.getAcks();
for(BatchId ack : b.getAcks()) { for(BatchId ack : acks) {
acks++;
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
db.removeAckedBatch(txn, c, ack); db.removeAckedBatch(txn, c, ack);
@@ -463,7 +468,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + acks + " acks"); LOG.fine("Received " + acks.size() + " acks");
} finally { } finally {
messageStatusLock.writeLock().unlock(); messageStatusLock.writeLock().unlock();
} }
@@ -481,14 +486,12 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
// FIXME: Replace clearSubs and addSub with setSubs
db.clearSubscriptions(txn, c); db.clearSubscriptions(txn, c);
int subs = 0; Set<GroupId> subs = h.getSubscriptions();
for(GroupId g : b.getSubscriptions()) { for(GroupId sub : subs) db.addSubscription(txn, c, sub);
subs++;
db.addSubscription(txn, c, g);
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + subs + " subscriptions"); LOG.fine("Received " + subs.size() + " subscriptions");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -508,7 +511,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { 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); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -522,7 +529,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
// Store the messages // Store the messages
int batches = 0; int batches = 0;
for(Batch batch : b.getBatches()) { for(Batch batch = b.getNextBatch(); batch != null; batch = b.getNextBatch()) {
batches++; batches++;
waitForPermissionToWrite(); waitForPermissionToWrite();
contactLock.readLock().lock(); contactLock.readLock().lock();
@@ -579,7 +586,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
lost = db.addReceivedBundle(txn, c, b.getId()); lost = db.addReceivedBundle(txn, c, h.getId());
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);

View File

@@ -1,9 +1,10 @@
package net.sf.briar.db; package net.sf.briar.db;
import java.io.IOException;
import java.security.SignatureException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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.Bundle;
import net.sf.briar.api.protocol.BundleBuilder; import net.sf.briar.api.protocol.BundleBuilder;
import net.sf.briar.api.protocol.GroupId; 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.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
@@ -48,8 +51,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
@Inject @Inject
SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner, SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
super(db, cleaner, batchBuilderProvider); super(db, cleaner, headerBuilderProvider, batchBuilderProvider);
} }
public void close() throws DbException { public void close() throws DbException {
@@ -140,21 +144,20 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
public Bundle generateBundle(ContactId c, BundleBuilder b) public Bundle generateBundle(ContactId c, BundleBuilder b)
throws DbException { throws DbException, IOException, SignatureException {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c); if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
// Ack all batches received from c HeaderBuilder h;
// Add acks
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
h = headerBuilderProvider.get();
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
int numAcks = 0; Set<BatchId> acks = db.removeBatchesToAck(txn, c);
for(BatchId ack : db.removeBatchesToAck(txn, c)) { h.addAcks(acks);
b.addAck(ack);
numAcks++;
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + numAcks + " acks"); LOG.fine("Added " + acks.size() + " acks");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -162,19 +165,16 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
} }
// Add a list of subscriptions // Add subscriptions
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
synchronized(subscriptionLock) { synchronized(subscriptionLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
int numSubs = 0; Set<GroupId> subs = db.getSubscriptions(txn);
for(GroupId g : db.getSubscriptions(txn)) { h.addSubscriptions(subs);
b.addSubscription(g);
numSubs++;
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + numSubs + " subscriptions"); LOG.fine("Added " + subs.size() + " subscriptions");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -188,14 +188,10 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
synchronized(transportLock) { synchronized(transportLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
int numTransports = 0;
Map<String, String> transports = db.getTransports(txn); Map<String, String> transports = db.getTransports(txn);
for(Entry<String, String> e : transports.entrySet()) { h.addTransports(transports);
b.addTransport(e.getKey(), e.getValue());
numTransports++;
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Added " + numTransports + " transports"); LOG.fine("Added " + transports.size() + " transports");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); 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(); long capacity = b.getCapacity();
capacity -= header.getSize();
b.addHeader(header);
// Add as many messages as possible to the bundle
while(true) { while(true) {
Batch batch = fillBatch(c, capacity); Batch batch = fillBatch(c, capacity);
if(batch == null) break; // No more messages to send if(batch == null) break; // No more messages to send
@@ -213,7 +213,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
capacity -= size; capacity -= size;
// If the batch is less than half full, stop trying - there may be // If the batch is less than half full, stop trying - there may be
// more messages trickling in but we can't wait forever // 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(); Bundle bundle = b.build();
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
@@ -222,14 +222,15 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
return bundle; return bundle;
} }
private Batch fillBatch(ContactId c, long capacity) throws DbException { private Batch fillBatch(ContactId c, long capacity) throws DbException,
SignatureException {
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
capacity = Math.min(capacity, Batch.CAPACITY); capacity = Math.min(capacity, Batch.MAX_SIZE);
Iterator<MessageId> it = Iterator<MessageId> it =
db.getSendableMessages(txn, c, capacity).iterator(); db.getSendableMessages(txn, c, capacity).iterator();
if(!it.hasNext()) { if(!it.hasNext()) {
@@ -252,6 +253,9 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
throw e; 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)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received bundle from " + c + ", " LOG.fine("Received bundle from " + c + ", "
+ b.getSize() + " bytes"); + b.getSize() + " bytes");
Header h;
// Mark all messages in acked batches as seen // Mark all messages in acked batches as seen
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) throw new NoSuchContactException(); if(!containsContact(c)) throw new NoSuchContactException();
h = b.getHeader();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
int acks = 0; Set<BatchId> acks = h.getAcks();
for(BatchId ack : b.getAcks()) { for(BatchId ack : acks) {
acks++;
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
db.removeAckedBatch(txn, c, ack); db.removeAckedBatch(txn, c, ack);
@@ -353,7 +359,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
} }
if(LOG.isLoggable(Level.FINE)) 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) { synchronized(subscriptionLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
// FIXME: Replace clearSubs and addSub with setSubs
db.clearSubscriptions(txn, c); db.clearSubscriptions(txn, c);
int subs = 0; Set<GroupId> subs = h.getSubscriptions();
for(GroupId g : b.getSubscriptions()) { for(GroupId sub : subs) db.addSubscription(txn, c, sub);
subs++;
db.addSubscription(txn, c, g);
}
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Received " + subs + " subscriptions"); LOG.fine("Received " + subs.size() + " subscriptions");
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -384,7 +388,11 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
synchronized(transportLock) { synchronized(transportLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { 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); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);
@@ -394,7 +402,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
// Store the messages // Store the messages
int batches = 0; int batches = 0;
for(Batch batch : b.getBatches()) { for(Batch batch = b.getNextBatch(); batch != null; batch = b.getNextBatch()) {
batches++; batches++;
waitForPermissionToWrite(); waitForPermissionToWrite();
synchronized(contactLock) { synchronized(contactLock) {
@@ -436,7 +444,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
lost = db.addReceivedBundle(txn, c, b.getId()); lost = db.addReceivedBundle(txn, c, h.getId());
db.commitTransaction(txn); db.commitTransaction(txn);
} catch(DbException e) { } catch(DbException e) {
db.abortTransaction(txn); db.abortTransaction(txn);

View File

@@ -1,8 +1,6 @@
package net.sf.briar.protocol; package net.sf.briar.protocol;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId; 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. */ /** A simple in-memory implementation of a batch. */
class BatchImpl implements Batch { class BatchImpl implements Batch {
private final List<Message> messages = new ArrayList<Message>(); private final BatchId id;
private BatchId id = null; private final long size;
private long size = 0L; private final List<Message> messages;
private final byte[] signature;
public void seal() { BatchImpl(BatchId id, long size, List<Message> messages, byte[] signature) {
// FIXME: Calculate batch ID this.id = id;
byte[] b = new byte[BatchId.LENGTH]; this.size = size;
new Random().nextBytes(b); this.messages = messages;
id = new BatchId(b); this.signature = signature;
} }
public BatchId getId() { public BatchId getId() {
@@ -34,8 +33,7 @@ class BatchImpl implements Batch {
return messages; return messages;
} }
public void addMessage(Message m) { public byte[] getSignature() {
messages.add(m); return signature;
size += m.getSize();
} }
} }

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -5,6 +5,7 @@ import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
/** A simple in-memory implementation of a message. */
public class MessageImpl implements Message { public class MessageImpl implements Message {
private final MessageId id, parent; private final MessageId id, parent;
@@ -53,7 +54,7 @@ public class MessageImpl implements Message {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof MessageImpl && id.equals(((MessageImpl)o).id); return o instanceof Message && id.equals(((Message)o).getId());
} }
@Override @Override

View File

@@ -1,10 +1,8 @@
package net.sf.briar.protocol; package net.sf.briar.protocol;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.Message;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides;
public class ProtocolModule extends AbstractModule { public class ProtocolModule extends AbstractModule {
@@ -12,9 +10,4 @@ public class ProtocolModule extends AbstractModule {
protected void configure() { protected void configure() {
bind(Message.class).to(MessageImpl.class); bind(Message.class).to(MessageImpl.class);
} }
@Provides
Batch createBatch() {
return new BatchImpl();
}
} }

View File

@@ -20,6 +20,8 @@
<test name='net.sf.briar.i18n.FontManagerTest'/> <test name='net.sf.briar.i18n.FontManagerTest'/>
<test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.i18n.I18nTest'/>
<test name='net.sf.briar.invitation.InvitationWorkerTest'/> <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.ReaderImplTest'/>
<test name='net.sf.briar.serial.WriterImplTest'/> <test name='net.sf.briar.serial.WriterImplTest'/>
<test name='net.sf.briar.setup.SetupWorkerTest'/> <test name='net.sf.briar.setup.SetupWorkerTest'/>

View File

@@ -7,6 +7,7 @@ import java.util.Collections;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.protocol.BatchBuilder; 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.api.protocol.MessageId;
import net.sf.briar.db.DatabaseCleaner.Callback; import net.sf.briar.db.DatabaseCleaner.Callback;
@@ -24,6 +25,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
protected abstract <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( protected abstract <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider); Provider<BatchBuilder> batchBuilderProvider);
@Test @Test
@@ -33,14 +35,17 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).getFreeSpace(); oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE)); will(returnValue(MIN_FREE_SPACE));
}}); }});
Callback db = createDatabaseComponentImpl(database, cleaner, Callback db = createDatabaseComponentImpl(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.checkFreeSpaceAndClean(); db.checkFreeSpaceAndClean();
@@ -54,8 +59,11 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).getFreeSpace(); oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1)); will(returnValue(MIN_FREE_SPACE - 1));
@@ -69,7 +77,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
will(returnValue(MIN_FREE_SPACE)); will(returnValue(MIN_FREE_SPACE));
}}); }});
Callback db = createDatabaseComponentImpl(database, cleaner, Callback db = createDatabaseComponentImpl(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.checkFreeSpaceAndClean(); db.checkFreeSpaceAndClean();
@@ -84,8 +92,11 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).getFreeSpace(); oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1)); will(returnValue(MIN_FREE_SPACE - 1));
@@ -101,7 +112,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
will(returnValue(MIN_FREE_SPACE)); will(returnValue(MIN_FREE_SPACE));
}}); }});
Callback db = createDatabaseComponentImpl(database, cleaner, Callback db = createDatabaseComponentImpl(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.checkFreeSpaceAndClean(); db.checkFreeSpaceAndClean();
@@ -116,8 +127,11 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).getFreeSpace(); oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1)); will(returnValue(MIN_FREE_SPACE - 1));
@@ -135,7 +149,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
will(returnValue(MIN_FREE_SPACE)); will(returnValue(MIN_FREE_SPACE));
}}); }});
Callback db = createDatabaseComponentImpl(database, cleaner, Callback db = createDatabaseComponentImpl(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.checkFreeSpaceAndClean(); db.checkFreeSpaceAndClean();

View File

@@ -1,5 +1,7 @@
package net.sf.briar.db; package net.sf.briar.db;
import java.io.IOException;
import java.security.SignatureException;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; 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.BundleBuilder;
import net.sf.briar.api.protocol.BundleId; import net.sf.briar.api.protocol.BundleId;
import net.sf.briar.api.protocol.GroupId; 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.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.protocol.MessageImpl; import net.sf.briar.protocol.MessageImpl;
@@ -32,8 +36,6 @@ import com.google.inject.Provider;
public abstract class DatabaseComponentTest extends TestCase { public abstract class DatabaseComponentTest extends TestCase {
private static final int ONE_MEGABYTE = 1024 * 1024;
protected final Object txn = new Object(); protected final Object txn = new Object();
protected final AuthorId authorId; protected final AuthorId authorId;
protected final BatchId batchId; protected final BatchId batchId;
@@ -45,6 +47,11 @@ public abstract class DatabaseComponentTest extends TestCase {
private final int size; private final int size;
private final byte[] body; private final byte[] body;
private final Message message; 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() { public DatabaseComponentTest() {
super(); super();
@@ -60,26 +67,32 @@ public abstract class DatabaseComponentTest extends TestCase {
body = new byte[size]; body = new byte[size];
message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId, message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId,
timestamp, body); 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( protected abstract <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider); Provider<BatchBuilder> batchBuilderProvider);
@Test @Test
public void testSimpleCalls() throws DbException { public void testSimpleCalls() throws DbException {
final Map<String, String> transports =
Collections.singletonMap("foo", "bar");
final Map<String, String> transports1 = final Map<String, String> transports1 =
Collections.singletonMap("foo", "bar baz"); Collections.singletonMap("foo", "bar baz");
final Set<GroupId> subs = Collections.singleton(groupId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(database).startTransaction(); allowing(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -95,7 +108,7 @@ public abstract class DatabaseComponentTest extends TestCase {
will(returnValue(contactId)); will(returnValue(contactId));
// getContacts() // getContacts()
oneOf(database).getContacts(txn); oneOf(database).getContacts(txn);
will(returnValue(Collections.singleton(contactId))); will(returnValue(contacts));
// getTransports(contactId) // getTransports(contactId)
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
@@ -119,16 +132,16 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).close(); oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.open(false); db.open(false);
assertEquals(Rating.UNRATED, db.getRating(authorId)); assertEquals(Rating.UNRATED, db.getRating(authorId));
assertEquals(contactId, db.addContact(transports)); assertEquals(contactId, db.addContact(transports));
assertEquals(Collections.singleton(contactId), db.getContacts()); assertEquals(contacts, db.getContacts());
assertEquals(transports, db.getTransports(contactId)); assertEquals(transports, db.getTransports(contactId));
db.setTransports(contactId, transports1); db.setTransports(contactId, transports1);
db.subscribe(groupId); db.subscribe(groupId);
assertEquals(Collections.singleton(groupId), db.getSubscriptions()); assertEquals(subs, db.getSubscriptions());
db.unsubscribe(groupId); db.unsubscribe(groupId);
db.removeContact(contactId); db.removeContact(contactId);
db.close(); db.close();
@@ -138,14 +151,16 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testNoParentStopsBackwardInclusion() throws DbException { public void testNoParentStopsBackwardInclusion() throws DbException {
final Set<MessageId> messages = Collections.singleton(messageId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
allowing(database).startTransaction(); allowing(database).startTransaction();
@@ -163,7 +178,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
@@ -172,14 +187,16 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testMissingParentStopsBackwardInclusion() throws DbException { public void testMissingParentStopsBackwardInclusion() throws DbException {
final Set<MessageId> messages = Collections.singleton(messageId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -200,7 +217,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
@@ -210,14 +227,16 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testChangingGroupsStopsBackwardInclusion() throws DbException { public void testChangingGroupsStopsBackwardInclusion() throws DbException {
final GroupId groupId1 = new GroupId(TestUtils.getRandomId()); final GroupId groupId1 = new GroupId(TestUtils.getRandomId());
final Set<MessageId> messages = Collections.singleton(messageId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -242,7 +261,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
@@ -252,14 +271,16 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testUnaffectedParentStopsBackwardInclusion() public void testUnaffectedParentStopsBackwardInclusion()
throws DbException { throws DbException {
final Set<MessageId> messages = Collections.singleton(messageId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -287,7 +308,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
@@ -297,14 +318,16 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testAffectedParentContinuesBackwardInclusion() public void testAffectedParentContinuesBackwardInclusion()
throws DbException { throws DbException {
final Set<MessageId> messages = Collections.singleton(messageId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -334,7 +357,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
@@ -349,8 +372,11 @@ public abstract class DatabaseComponentTest extends TestCase {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -360,7 +386,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
@@ -374,8 +400,11 @@ public abstract class DatabaseComponentTest extends TestCase {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -387,7 +416,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
@@ -401,8 +430,11 @@ public abstract class DatabaseComponentTest extends TestCase {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -412,7 +444,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).addMessage(txn, message); oneOf(database).addMessage(txn, message);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getContacts(txn); oneOf(database).getContacts(txn);
will(returnValue(Collections.singleton(contactId))); will(returnValue(contacts));
oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); oneOf(database).setStatus(txn, contactId, messageId, Status.NEW);
// The author is unrated and there are no sendable children // The author is unrated and there are no sendable children
oneOf(database).getRating(txn, authorId); oneOf(database).getRating(txn, authorId);
@@ -423,7 +455,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
@@ -438,8 +470,11 @@ public abstract class DatabaseComponentTest extends TestCase {
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -449,7 +484,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).addMessage(txn, message); oneOf(database).addMessage(txn, message);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getContacts(txn); oneOf(database).getContacts(txn);
will(returnValue(Collections.singleton(contactId))); will(returnValue(contacts));
oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); oneOf(database).setStatus(txn, contactId, messageId, Status.NEW);
// The author is rated GOOD and there are two sendable children // The author is rated GOOD and there are two sendable children
oneOf(database).getRating(txn, authorId); oneOf(database).getRating(txn, authorId);
@@ -463,7 +498,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
@@ -472,14 +507,17 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testGenerateBundleThrowsExceptionIfContactIsMissing() public void testGenerateBundleThrowsExceptionIfContactIsMissing()
throws DbException { throws DbException, IOException, SignatureException {
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class); final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check that the contact is still in the DB // Check that the contact is still in the DB
@@ -490,7 +528,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
try { try {
db.generateBundle(contactId, bundleBuilder); db.generateBundle(contactId, bundleBuilder);
@@ -501,15 +539,22 @@ public abstract class DatabaseComponentTest extends TestCase {
} }
@Test @Test
public void testGenerateBundle() throws DbException { public void testGenerateBundle() throws DbException, IOException,
SignatureException {
final long headerSize = 1234L;
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class); 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 BatchBuilder batchBuilder = context.mock(BatchBuilder.class);
final Batch batch = context.mock(Batch.class); final Batch batch = context.mock(Batch.class);
final Bundle bundle = context.mock(Bundle.class); final Bundle bundle = context.mock(Bundle.class);
@@ -519,24 +564,33 @@ public abstract class DatabaseComponentTest extends TestCase {
allowing(database).commitTransaction(txn); allowing(database).commitTransaction(txn);
allowing(database).containsContact(txn, contactId); allowing(database).containsContact(txn, contactId);
will(returnValue(true)); 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); oneOf(database).removeBatchesToAck(txn, contactId);
will(returnValue(Collections.singleton(batchId))); will(returnValue(acks));
oneOf(bundleBuilder).addAck(batchId); oneOf(headerBuilder).addAcks(acks);
// Add subscriptions to the bundle // Add subscriptions to the header
oneOf(database).getSubscriptions(txn); oneOf(database).getSubscriptions(txn);
will(returnValue(Collections.singleton(groupId))); will(returnValue(subs));
oneOf(bundleBuilder).addSubscription(groupId); oneOf(headerBuilder).addSubscriptions(subs);
// Add transports to the bundle // Add transports to the header
oneOf(database).getTransports(txn); oneOf(database).getTransports(txn);
will(returnValue(Collections.singletonMap("foo", "bar"))); will(returnValue(transports));
oneOf(bundleBuilder).addTransport("foo", "bar"); oneOf(headerBuilder).addTransports(transports);
// Prepare to add batches to the bundle // Build the header
oneOf(headerBuilder).build();
will(returnValue(header));
oneOf(bundleBuilder).getCapacity(); oneOf(bundleBuilder).getCapacity();
will(returnValue((long) ONE_MEGABYTE)); will(returnValue(1024L * 1024L));
// Add messages to the batch oneOf(header).getSize();
oneOf(database).getSendableMessages(txn, contactId, Batch.CAPACITY); will(returnValue(headerSize));
will(returnValue(Collections.singleton(messageId))); 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(); oneOf(batchBuilderProvider).get();
will(returnValue(batchBuilder)); will(returnValue(batchBuilder));
oneOf(database).getMessage(txn, messageId); oneOf(database).getMessage(txn, messageId);
@@ -547,8 +601,8 @@ public abstract class DatabaseComponentTest extends TestCase {
// Record the batch as outstanding // Record the batch as outstanding
oneOf(batch).getId(); oneOf(batch).getId();
will(returnValue(batchId)); will(returnValue(batchId));
oneOf(database).addOutstandingBatch(txn, contactId, batchId, oneOf(database).addOutstandingBatch(
Collections.singleton(messageId)); txn, contactId, batchId, messages);
// Add the batch to the bundle // Add the batch to the bundle
oneOf(bundleBuilder).addBatch(batch); oneOf(bundleBuilder).addBatch(batch);
// Check whether to add another batch // Check whether to add another batch
@@ -559,7 +613,7 @@ public abstract class DatabaseComponentTest extends TestCase {
will(returnValue(bundle)); will(returnValue(bundle));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.generateBundle(contactId, bundleBuilder); db.generateBundle(contactId, bundleBuilder);
@@ -568,14 +622,17 @@ public abstract class DatabaseComponentTest extends TestCase {
@Test @Test
public void testReceiveBundleThrowsExceptionIfContactIsMissing() public void testReceiveBundleThrowsExceptionIfContactIsMissing()
throws DbException { throws DbException, IOException, SignatureException {
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
final Bundle bundle = context.mock(Bundle.class); final Bundle bundle = context.mock(Bundle.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check that the contact is still in the DB // Check that the contact is still in the DB
@@ -586,7 +643,7 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
try { try {
db.receiveBundle(contactId, bundle); db.receiveBundle(contactId, bundle);
@@ -597,17 +654,20 @@ public abstract class DatabaseComponentTest extends TestCase {
} }
@Test @Test
public void testReceivedBundle() throws DbException { public void testReceivedBundle() throws DbException, IOException,
final Map<String, String> transports = SignatureException {
Collections.singletonMap("foo", "bar");
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider = final Provider<HeaderBuilder> headerBuilderProvider =
context.mock(Provider.class); context.mock(Provider.class);
@SuppressWarnings("unchecked")
final Provider<BatchBuilder> batchBuilderProvider =
context.mock(Provider.class, "batchBuilderProvider");
final Bundle bundle = context.mock(Bundle.class); final Bundle bundle = context.mock(Bundle.class);
final Header header = context.mock(Header.class);
final Batch batch = context.mock(Batch.class); final Batch batch = context.mock(Batch.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(database).startTransaction(); allowing(database).startTransaction();
@@ -615,22 +675,25 @@ public abstract class DatabaseComponentTest extends TestCase {
allowing(database).commitTransaction(txn); allowing(database).commitTransaction(txn);
allowing(database).containsContact(txn, contactId); allowing(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
// Header
oneOf(bundle).getHeader();
will(returnValue(header));
// Acks // Acks
oneOf(bundle).getAcks(); oneOf(header).getAcks();
will(returnValue(Collections.singleton(batchId))); will(returnValue(acks));
oneOf(database).removeAckedBatch(txn, contactId, batchId); oneOf(database).removeAckedBatch(txn, contactId, batchId);
// Subscriptions // Subscriptions
oneOf(database).clearSubscriptions(txn, contactId); oneOf(database).clearSubscriptions(txn, contactId);
oneOf(bundle).getSubscriptions(); oneOf(header).getSubscriptions();
will(returnValue(Collections.singleton(groupId))); will(returnValue(subs));
oneOf(database).addSubscription(txn, contactId, groupId); oneOf(database).addSubscription(txn, contactId, groupId);
// Transports // Transports
oneOf(bundle).getTransports(); oneOf(header).getTransports();
will(returnValue(transports)); will(returnValue(transports));
oneOf(database).setTransports(txn, contactId, transports); oneOf(database).setTransports(txn, contactId, transports);
// Batches // Batches
oneOf(bundle).getBatches(); oneOf(bundle).getNextBatch();
will(returnValue(Collections.singleton(batch))); will(returnValue(batch));
oneOf(batch).getMessages(); oneOf(batch).getMessages();
will(returnValue(Collections.singleton(message))); will(returnValue(Collections.singleton(message)));
oneOf(database).containsSubscription(txn, groupId); oneOf(database).containsSubscription(txn, groupId);
@@ -642,15 +705,18 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(batch).getId(); oneOf(batch).getId();
will(returnValue(batchId)); will(returnValue(batchId));
oneOf(database).addBatchToAck(txn, contactId, batchId); oneOf(database).addBatchToAck(txn, contactId, batchId);
// Any more batches? Nope
oneOf(bundle).getNextBatch();
will(returnValue(null));
// Lost batches // Lost batches
oneOf(bundle).getId(); oneOf(header).getId();
will(returnValue(bundleId)); will(returnValue(bundleId));
oneOf(database).addReceivedBundle(txn, contactId, bundleId); oneOf(database).addReceivedBundle(txn, contactId, bundleId);
will(returnValue(Collections.singleton(batchId))); will(returnValue(Collections.singleton(batchId)));
oneOf(database).removeLostBatch(txn, contactId, batchId); oneOf(database).removeLostBatch(txn, contactId, batchId);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
db.receiveBundle(contactId, bundle); db.receiveBundle(contactId, bundle);

View File

@@ -2,6 +2,7 @@ package net.sf.briar.db;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.protocol.BatchBuilder; import net.sf.briar.api.protocol.BatchBuilder;
import net.sf.briar.api.protocol.HeaderBuilder;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -11,16 +12,18 @@ extends DatabaseComponentImplTest {
@Override @Override
protected <T> DatabaseComponent createDatabaseComponent( protected <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
return createDatabaseComponentImpl(database, cleaner, return createDatabaseComponentImpl(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
} }
@Override @Override
protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
return new ReadWriteLockDatabaseComponent<T>(database, cleaner, return new ReadWriteLockDatabaseComponent<T>(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
} }
} }

View File

@@ -2,6 +2,7 @@ package net.sf.briar.db;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.protocol.BatchBuilder; import net.sf.briar.api.protocol.BatchBuilder;
import net.sf.briar.api.protocol.HeaderBuilder;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -11,16 +12,18 @@ extends DatabaseComponentImplTest {
@Override @Override
protected <T> DatabaseComponent createDatabaseComponent( protected <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
return createDatabaseComponentImpl(database, cleaner, return createDatabaseComponentImpl(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
} }
@Override @Override
protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<HeaderBuilder> headerBuilderProvider,
Provider<BatchBuilder> batchBuilderProvider) { Provider<BatchBuilder> batchBuilderProvider) {
return new SynchronizedDatabaseComponent<T>(database, cleaner, return new SynchronizedDatabaseComponent<T>(database, cleaner,
batchBuilderProvider); headerBuilderProvider, batchBuilderProvider);
} }
} }

View File

@@ -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;
}
};
}
}

View File

@@ -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;
}
};
}
}

View File

@@ -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;
}
}