Bundles are gone - the batch-mode and stream-mode protocols now

consist of independently encrypted and authenticated packets (Ack,
Batch, Subscriptions and Transports so far).
This commit is contained in:
akwizgran
2011-07-22 22:19:24 +01:00
parent 5d000b62f8
commit de648daca5
42 changed files with 1309 additions and 1716 deletions

View File

@@ -20,11 +20,10 @@
<test name='net.sf.briar.i18n.FontManagerTest'/>
<test name='net.sf.briar.i18n.I18nTest'/>
<test name='net.sf.briar.invitation.InvitationWorkerTest'/>
<test name='net.sf.briar.protocol.AckReaderTest'/>
<test name='net.sf.briar.protocol.BatchReaderTest'/>
<test name='net.sf.briar.protocol.BundleReaderImplTest'/>
<test name='net.sf.briar.protocol.BundleReadWriteTest'/>
<test name='net.sf.briar.protocol.ConsumersTest'/>
<test name='net.sf.briar.protocol.HeaderReaderTest'/>
<test name='net.sf.briar.protocol.FileReadWriteTest'/>
<test name='net.sf.briar.protocol.SigningDigestingOutputStreamTest'/>
<test name='net.sf.briar.serial.ReaderImplTest'/>
<test name='net.sf.briar.serial.WriterImplTest'/>

View File

@@ -1,8 +1,9 @@
package net.sf.briar.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import junit.framework.TestCase;
import net.sf.briar.TestUtils;
@@ -12,17 +13,13 @@ import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.AckWriter;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.BundleReader;
import net.sf.briar.api.protocol.BundleWriter;
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.protocol.MessageId;
import net.sf.briar.api.serial.Raw;
import net.sf.briar.api.serial.RawByteArray;
import org.jmock.Expectations;
import org.jmock.Mockery;
@@ -40,11 +37,11 @@ public abstract class DatabaseComponentTest extends TestCase {
private final int size;
private final byte[] raw;
private final Message message;
private final Set<ContactId> contacts;
private final Set<BatchId> acks;
private final Set<GroupId> subs;
private final Collection<ContactId> contacts;
private final Collection<BatchId> acks;
private final Collection<GroupId> subs;
private final Map<String, String> transports;
private final Set<MessageId> messages;
private final Collection<MessageId> messages;
public DatabaseComponentTest() {
super();
@@ -59,11 +56,11 @@ public abstract class DatabaseComponentTest extends TestCase {
raw = new byte[size];
message = new TestMessage(messageId, MessageId.NONE, groupId, authorId,
timestamp, raw);
contacts = Collections.singleton(contactId);
acks = Collections.singleton(batchId);
subs = Collections.singleton(groupId);
contacts = Collections.singletonList(contactId);
acks = Collections.singletonList(batchId);
subs = Collections.singletonList(groupId);
transports = Collections.singletonMap("foo", "bar");
messages = Collections.singleton(messageId);
messages = Collections.singletonList(messageId);
}
protected abstract <T> DatabaseComponent createDatabaseComponent(
@@ -419,13 +416,13 @@ public abstract class DatabaseComponentTest extends TestCase {
}
@Test
public void testGenerateBundleThrowsExceptionIfContactIsMissing()
public void testGenerateAckThrowsExceptionIfContactIsMissing()
throws Exception {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final BundleWriter bundleBuilder = context.mock(BundleWriter.class);
final AckWriter ackWriter = context.mock(AckWriter.class);
context.checking(new Expectations() {{
// Check that the contact is still in the DB
oneOf(database).startTransaction();
@@ -437,7 +434,7 @@ public abstract class DatabaseComponentTest extends TestCase {
DatabaseComponent db = createDatabaseComponent(database, cleaner);
try {
db.generateBundle(contactId, bundleBuilder);
db.generateAck(contactId, ackWriter);
assertTrue(false);
} catch(NoSuchContactException expected) {}
@@ -445,67 +442,49 @@ public abstract class DatabaseComponentTest extends TestCase {
}
@Test
public void testGenerateBundle() throws Exception {
final long headerSize = 1234L;
final Raw messageRaw = new RawByteArray(raw);
public void testGenerateAck() throws Exception {
final BatchId batchId1 = new BatchId(TestUtils.getRandomId());
final Collection<BatchId> twoAcks = new ArrayList<BatchId>();
twoAcks.add(batchId);
twoAcks.add(batchId1);
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final BundleWriter bundleWriter = context.mock(BundleWriter.class);
final AckWriter ackWriter = context.mock(AckWriter.class);
context.checking(new Expectations() {{
allowing(database).startTransaction();
will(returnValue(txn));
allowing(database).commitTransaction(txn);
allowing(database).containsContact(txn, contactId);
will(returnValue(true));
// Add acks to the header
oneOf(database).removeBatchesToAck(txn, contactId);
will(returnValue(acks));
// Add subscriptions to the header
oneOf(database).getSubscriptions(txn);
will(returnValue(subs));
// Add transports to the header
oneOf(database).getTransports(txn);
will(returnValue(transports));
// Build the header
oneOf(bundleWriter).addHeader(acks, subs, transports);
// Add a batch to the bundle
oneOf(bundleWriter).getRemainingCapacity();
will(returnValue(1024L * 1024L - headerSize));
oneOf(database).getSendableMessages(txn, contactId,
Batch.MAX_SIZE - headerSize);
will(returnValue(messages));
oneOf(database).getMessage(txn, messageId);
will(returnValue(raw));
// Add the batch to the bundle
oneOf(bundleWriter).addBatch(Collections.singletonList(messageRaw));
will(returnValue(batchId));
// Record the outstanding batch
oneOf(database).addOutstandingBatch(
txn, contactId, batchId, messages);
// Send the bundle
oneOf(bundleWriter).finish();
// Get the batches to ack
oneOf(database).getBatchesToAck(txn, contactId);
will(returnValue(twoAcks));
// Try to add both batches to the writer - only manage to add one
oneOf(ackWriter).addBatchId(batchId);
will(returnValue(true));
oneOf(ackWriter).addBatchId(batchId1);
will(returnValue(false));
// Record the batch that was acked
oneOf(database).removeBatchesToAck(txn, contactId, acks);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner);
db.generateBundle(contactId, bundleWriter);
db.generateAck(contactId, ackWriter);
context.assertIsSatisfied();
}
@Test
public void testReceiveBundleThrowsExceptionIfContactIsMissing()
public void testReceiveAckThrowsExceptionIfContactIsMissing()
throws Exception {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final BundleReader bundleReader = context.mock(BundleReader.class);
final Header header = context.mock(Header.class);
final Ack ack = context.mock(Ack.class);
context.checking(new Expectations() {{
oneOf(bundleReader).getHeader();
will(returnValue(header));
// Check that the contact is still in the DB
oneOf(database).startTransaction();
will(returnValue(txn));
@@ -516,7 +495,7 @@ public abstract class DatabaseComponentTest extends TestCase {
DatabaseComponent db = createDatabaseComponent(database, cleaner);
try {
db.receiveBundle(contactId, bundleReader);
db.receiveAck(contactId, ack);
assertTrue(false);
} catch(NoSuchContactException expected) {}
@@ -524,66 +503,26 @@ public abstract class DatabaseComponentTest extends TestCase {
}
@Test
public void testReceiveBundle() throws Exception {
public void testReceiveAck() throws Exception {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
final BundleReader bundleReader = context.mock(BundleReader.class);
final Header header = context.mock(Header.class);
final Batch batch = context.mock(Batch.class);
final Ack ack = context.mock(Ack.class);
context.checking(new Expectations() {{
allowing(database).startTransaction();
will(returnValue(txn));
allowing(database).commitTransaction(txn);
allowing(database).containsContact(txn, contactId);
will(returnValue(true));
// Header
oneOf(bundleReader).getHeader();
will(returnValue(header));
// Acks
oneOf(header).getAcks();
// Get the acked batches
oneOf(ack).getBatches();
will(returnValue(acks));
oneOf(database).removeAckedBatch(txn, contactId, batchId);
// Subscriptions
oneOf(header).getSubscriptions();
will(returnValue(subs));
oneOf(header).getTimestamp();
will(returnValue(timestamp));
oneOf(database).setSubscriptions(txn, contactId, subs, timestamp);
// Transports
oneOf(header).getTransports();
will(returnValue(transports));
oneOf(header).getTimestamp();
will(returnValue(timestamp));
oneOf(database).setTransports(txn, contactId, transports,
timestamp);
// Batches
oneOf(bundleReader).getNextBatch();
will(returnValue(batch));
oneOf(batch).getMessages();
will(returnValue(Collections.singleton(message)));
oneOf(database).containsSubscription(txn, groupId);
will(returnValue(true));
oneOf(database).addMessage(txn, message);
will(returnValue(false)); // Duplicate message
oneOf(database).setStatus(txn, contactId, messageId, Status.SEEN);
// Batch to ack
oneOf(batch).getId();
will(returnValue(batchId));
oneOf(database).addBatchToAck(txn, contactId, batchId);
// Any more batches? Nope
oneOf(bundleReader).getNextBatch();
will(returnValue(null));
oneOf(bundleReader).finish();
// Lost batches
oneOf(database).getLostBatches(txn, contactId);
will(returnValue(Collections.singleton(batchId)));
oneOf(database).removeLostBatch(txn, contactId, batchId);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner);
db.receiveBundle(contactId, bundleReader);
db.receiveAck(contactId, ack);
context.assertIsSatisfied();
}

View File

@@ -3,12 +3,12 @@ package net.sf.briar.db;
import java.io.File;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -73,9 +73,8 @@ public class H2DatabaseTest extends TestCase {
@Test
public void testPersistence() throws DbException {
Database<Connection> db = open(false);
// Store some records
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId));
Map<String, String> transports = Collections.singletonMap("foo", "bar");
@@ -90,9 +89,8 @@ public class H2DatabaseTest extends TestCase {
db.commitTransaction(txn);
db.close();
// Reopen the database
db = open(true);
// Check that the records are still there
db = open(true);
txn = db.startTransaction();
assertTrue(db.containsContact(txn, contactId));
transports = db.getTransports(txn, contactId);
@@ -108,9 +106,8 @@ public class H2DatabaseTest extends TestCase {
db.commitTransaction(txn);
db.close();
// Repoen the database
db = open(true);
// Check that the records are gone
db = open(true);
txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId));
assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
@@ -126,9 +123,9 @@ public class H2DatabaseTest extends TestCase {
ContactId contactId2 = new ContactId(3);
ContactId contactId3 = new ContactId(4);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Create three contacts
Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId));
assertEquals(contactId, db.addContact(txn, null));
assertTrue(db.containsContact(txn, contactId));
@@ -145,199 +142,171 @@ public class H2DatabaseTest extends TestCase {
assertFalse(db.containsContact(txn, contactId3));
assertEquals(contactId3, db.addContact(txn, null));
assertTrue(db.containsContact(txn, contactId3));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testRatings() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Unknown authors should be unrated
assertEquals(Rating.UNRATED, db.getRating(txn, authorId));
// Store a rating
db.setRating(txn, authorId, Rating.GOOD);
db.commitTransaction(txn);
// Check that the rating was stored
txn = db.startTransaction();
assertEquals(Rating.GOOD, db.getRating(txn, authorId));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testUnsubscribingRemovesMessage() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Subscribe to a group and store a message
Connection txn = db.startTransaction();
db.addSubscription(txn, groupId);
db.addMessage(txn, message);
db.commitTransaction(txn);
// Unsubscribing from the group should delete the message
txn = db.startTransaction();
assertTrue(db.containsMessage(txn, messageId));
db.removeSubscription(txn, groupId);
assertFalse(db.containsMessage(txn, messageId));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testSendableMessagesMustBeSendable() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setStatus(txn, contactId, messageId, Status.NEW);
db.commitTransaction(txn);
// The message should not be sendable
txn = db.startTransaction();
assertEquals(0, db.getSendability(txn, messageId));
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
// Changing the sendability to > 0 should make the message sendable
txn = db.startTransaction();
db.setSendability(txn, messageId, 1);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
db.commitTransaction(txn);
// Changing the sendability to 0 should make the message unsendable
txn = db.startTransaction();
db.setSendability(txn, messageId, 0);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testSendableMessagesMustBeNew() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.commitTransaction(txn);
// The message has no status yet, so it should not be sendable
txn = db.startTransaction();
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
// Changing the status to Status.NEW should make the message sendable
txn = db.startTransaction();
db.setStatus(txn, contactId, messageId, Status.NEW);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
db.commitTransaction(txn);
// Changing the status to SENT should make the message unsendable
txn = db.startTransaction();
db.setStatus(txn, contactId, messageId, Status.SENT);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
// Changing the status to SEEN should also make the message unsendable
txn = db.startTransaction();
db.setStatus(txn, contactId, messageId, Status.SEEN);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testSendableMessagesMustBeSubscribed() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
db.commitTransaction(txn);
// The contact is not subscribed, so the message should not be sendable
txn = db.startTransaction();
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
// The contact subscribing should make the message sendable
txn = db.startTransaction();
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
db.commitTransaction(txn);
// The contact unsubscribing should make the message unsendable
txn = db.startTransaction();
db.setSubscriptions(txn, contactId, Collections.<GroupId>emptySet(), 2);
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testSendableMessagesMustFitCapacity() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
db.commitTransaction(txn);
// The message is too large to send
txn = db.startTransaction();
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, size - 1).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
// The message is just the right size to send
txn = db.startTransaction();
it = db.getSendableMessages(txn, contactId, size).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -345,47 +314,45 @@ public class H2DatabaseTest extends TestCase {
public void testBatchesToAck() throws DbException {
BatchId batchId1 = new BatchId(TestUtils.getRandomId());
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact and some batches to ack
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addBatchToAck(txn, contactId, batchId);
db.addBatchToAck(txn, contactId, batchId1);
db.commitTransaction(txn);
// Both batch IDs should be returned
txn = db.startTransaction();
Set<BatchId> acks = db.removeBatchesToAck(txn, contactId);
Collection<BatchId> acks = db.getBatchesToAck(txn, contactId);
assertEquals(2, acks.size());
assertTrue(acks.contains(batchId));
assertTrue(acks.contains(batchId1));
db.commitTransaction(txn);
// Remove the batch IDs
db.removeBatchesToAck(txn, contactId, acks);
// Both batch IDs should have been removed
txn = db.startTransaction();
acks = db.removeBatchesToAck(txn, contactId);
acks = db.getBatchesToAck(txn, contactId);
assertEquals(0, acks.size());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testRemoveAckedBatch() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
db.commitTransaction(txn);
// Get the message and mark it as sent
txn = db.startTransaction();
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
@@ -394,10 +361,8 @@ public class H2DatabaseTest extends TestCase {
db.setStatus(txn, contactId, messageId, Status.SENT);
db.addOutstandingBatch(txn, contactId, batchId,
Collections.singleton(messageId));
db.commitTransaction(txn);
// The message should no longer be sendable
txn = db.startTransaction();
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
// Pretend that the batch was acked
@@ -405,27 +370,25 @@ public class H2DatabaseTest extends TestCase {
// The message still should not be sendable
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testRemoveLostBatch() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, subscribe to a group and store a message
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
db.addSubscription(txn, groupId);
db.setSubscriptions(txn, contactId, Collections.singleton(groupId), 1);
db.addMessage(txn, message);
db.setSendability(txn, messageId, 1);
db.setStatus(txn, contactId, messageId, Status.NEW);
db.commitTransaction(txn);
// Get the message and mark it as sent
txn = db.startTransaction();
Iterator<MessageId> it =
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertTrue(it.hasNext());
@@ -434,10 +397,8 @@ public class H2DatabaseTest extends TestCase {
db.setStatus(txn, contactId, messageId, Status.SENT);
db.addOutstandingBatch(txn, contactId, batchId,
Collections.singleton(messageId));
db.commitTransaction(txn);
// The message should no longer be sendable
txn = db.startTransaction();
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
assertFalse(it.hasNext());
// Pretend that the batch was lost
@@ -447,8 +408,8 @@ public class H2DatabaseTest extends TestCase {
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -458,15 +419,15 @@ public class H2DatabaseTest extends TestCase {
for(int i = 0; i < ids.length; i++) {
ids[i] = new BatchId(TestUtils.getRandomId());
}
Set<MessageId> empty = Collections.emptySet();
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
// Add some outstanding batches, a few ms apart
for(int i = 0; i < ids.length; i++) {
db.addOutstandingBatch(txn, contactId, ids[i], empty);
db.addOutstandingBatch(txn, contactId, ids[i],
Collections.<MessageId>emptySet());
try {
Thread.sleep(5);
} catch(InterruptedException ignored) {}
@@ -475,19 +436,19 @@ public class H2DatabaseTest extends TestCase {
// RETRANSMIT_THRESHOLD - 1 acks should not trigger any retransmissions
for(int i = 0; i < Database.RETRANSMIT_THRESHOLD - 1; i++) {
db.removeAckedBatch(txn, contactId, ids[ids.length - i - 1]);
Set<BatchId> lost = db.getLostBatches(txn, contactId);
assertEquals(Collections.emptySet(), lost);
Collection<BatchId> lost = db.getLostBatches(txn, contactId);
assertEquals(Collections.emptyList(), lost);
}
// The next ack should trigger the retransmission of the remaining
// five outstanding batches
int index = ids.length - Database.RETRANSMIT_THRESHOLD;
db.removeAckedBatch(txn, contactId, ids[index]);
Set<BatchId> lost = db.getLostBatches(txn, contactId);
Collection<BatchId> lost = db.getLostBatches(txn, contactId);
for(int i = 0; i < index; i++) {
assertTrue(lost.contains(ids[i]));
}
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -497,15 +458,15 @@ public class H2DatabaseTest extends TestCase {
for(int i = 0; i < ids.length; i++) {
ids[i] = new BatchId(TestUtils.getRandomId());
}
Set<MessageId> empty = Collections.emptySet();
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact
Connection txn = db.startTransaction();
assertEquals(contactId, db.addContact(txn, null));
// Add some outstanding batches, a few ms apart
for(int i = 0; i < ids.length; i++) {
db.addOutstandingBatch(txn, contactId, ids[i], empty);
db.addOutstandingBatch(txn, contactId, ids[i],
Collections.<MessageId>emptySet());
try {
Thread.sleep(5);
} catch(InterruptedException ignored) {}
@@ -514,11 +475,11 @@ public class H2DatabaseTest extends TestCase {
// should be retransmitted
for(int i = 0; i < ids.length; i++) {
db.removeAckedBatch(txn, contactId, ids[i]);
Set<BatchId> lost = db.getLostBatches(txn, contactId);
assertEquals(Collections.emptySet(), lost);
Collection<BatchId> lost = db.getLostBatches(txn, contactId);
assertEquals(Collections.emptyList(), lost);
}
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -529,16 +490,14 @@ public class H2DatabaseTest extends TestCase {
Message message1 = new TestMessage(messageId1, MessageId.NONE, groupId,
authorId1, timestamp, raw);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Subscribe to a group and store two messages
Connection txn = db.startTransaction();
db.addSubscription(txn, groupId);
db.addMessage(txn, message);
db.addMessage(txn, message1);
db.commitTransaction(txn);
// Check that each message is retrievable via its author
txn = db.startTransaction();
Iterator<MessageId> it =
db.getMessagesByAuthor(txn, authorId).iterator();
assertTrue(it.hasNext());
@@ -548,8 +507,8 @@ public class H2DatabaseTest extends TestCase {
assertTrue(it.hasNext());
assertEquals(messageId1, it.next());
assertFalse(it.hasNext());
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -567,9 +526,9 @@ public class H2DatabaseTest extends TestCase {
Message child3 = new TestMessage(childId3, messageId, groupId1,
authorId, timestamp, raw);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Subscribe to the groups and store the messages
Connection txn = db.startTransaction();
db.addSubscription(txn, groupId);
db.addSubscription(txn, groupId1);
db.addMessage(txn, message);
@@ -580,17 +539,15 @@ public class H2DatabaseTest extends TestCase {
db.setSendability(txn, childId1, 1);
db.setSendability(txn, childId2, 5);
db.setSendability(txn, childId3, 3);
db.commitTransaction(txn);
// There should be two sendable children
txn = db.startTransaction();
assertEquals(2, db.getNumberOfSendableChildren(txn, messageId));
// Make one of the children unsendable
db.setSendability(txn, childId1, 0);
// Now there should be one sendable child
assertEquals(1, db.getNumberOfSendableChildren(txn, messageId));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -600,31 +557,27 @@ public class H2DatabaseTest extends TestCase {
Message message1 = new TestMessage(messageId1, MessageId.NONE, groupId,
authorId, timestamp + 1000, raw);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Subscribe to a group and store two messages
Connection txn = db.startTransaction();
db.addSubscription(txn, groupId);
db.addMessage(txn, message);
db.addMessage(txn, message1);
db.commitTransaction(txn);
// Allowing enough capacity for one message should return the older one
txn = db.startTransaction();
Iterator<MessageId> it = db.getOldMessages(txn, size).iterator();
assertTrue(it.hasNext());
assertEquals(messageId, it.next());
assertFalse(it.hasNext());
db.commitTransaction(txn);
// Allowing enough capacity for both messages should return both
txn = db.startTransaction();
Set<MessageId> ids = new HashSet<MessageId>();
Collection<MessageId> ids = new HashSet<MessageId>();
for(MessageId id : db.getOldMessages(txn, size * 2)) ids.add(id);
assertEquals(2, ids.size());
assertTrue(ids.contains(messageId));
assertTrue(ids.contains(messageId1));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@@ -732,9 +685,9 @@ public class H2DatabaseTest extends TestCase {
@Test
public void testUpdateTransports() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact with some transport details
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.singletonMap("foo", "bar");
assertEquals(contactId, db.addContact(txn, transports));
assertEquals(transports, db.getTransports(txn, contactId));
@@ -753,17 +706,17 @@ public class H2DatabaseTest extends TestCase {
// Remove the local transport details
db.setTransports(txn, null);
assertEquals(Collections.emptyMap(), db.getTransports(txn));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testTransportsNotUpdatedIfTimestampIsOld() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact with some transport details
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.singletonMap("foo", "bar");
assertEquals(contactId, db.addContact(txn, transports));
assertEquals(transports, db.getTransports(txn, contactId));
@@ -780,33 +733,35 @@ public class H2DatabaseTest extends TestCase {
db.setTransports(txn, contactId, transports2, 1);
// The old transports should still be there
assertEquals(transports1, db.getTransports(txn, contactId));
db.commitTransaction(txn);
db.commitTransaction(txn);
db.close();
}
@Test
public void testUpdateSubscriptions() throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.emptyMap();
assertEquals(contactId, db.addContact(txn, transports));
// Add some subscriptions
Set<GroupId> subs = new HashSet<GroupId>();
Collection<GroupId> subs = new HashSet<GroupId>();
subs.add(new GroupId(TestUtils.getRandomId()));
subs.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs, 1);
assertEquals(subs, db.getSubscriptions(txn, contactId));
assertEquals(subs,
new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
// Update the subscriptions
Set<GroupId> subs1 = new HashSet<GroupId>();
Collection<GroupId> subs1 = new HashSet<GroupId>();
subs1.add(new GroupId(TestUtils.getRandomId()));
subs1.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs1, 2);
assertEquals(subs1, db.getSubscriptions(txn, contactId));
db.commitTransaction(txn);
assertEquals(subs1,
new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
db.commitTransaction(txn);
db.close();
}
@@ -814,26 +769,28 @@ public class H2DatabaseTest extends TestCase {
public void testSubscriptionsNotUpdatedIfTimestampIsOld()
throws DbException {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact
Connection txn = db.startTransaction();
Map<String, String> transports = Collections.emptyMap();
assertEquals(contactId, db.addContact(txn, transports));
// Add some subscriptions
Set<GroupId> subs = new HashSet<GroupId>();
Collection<GroupId> subs = new HashSet<GroupId>();
subs.add(new GroupId(TestUtils.getRandomId()));
subs.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs, 2);
assertEquals(subs, db.getSubscriptions(txn, contactId));
assertEquals(subs,
new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
// Try to update the subscriptions using a timestamp of 1
Set<GroupId> subs1 = new HashSet<GroupId>();
Collection<GroupId> subs1 = new HashSet<GroupId>();
subs1.add(new GroupId(TestUtils.getRandomId()));
subs1.add(new GroupId(TestUtils.getRandomId()));
db.setSubscriptions(txn, contactId, subs1, 1);
// The old subscriptions should still be there
assertEquals(subs, db.getSubscriptions(txn, contactId));
db.commitTransaction(txn);
assertEquals(subs,
new HashSet<GroupId>(db.getSubscriptions(txn, contactId)));
db.commitTransaction(txn);
db.close();
}

View File

@@ -0,0 +1,129 @@
package net.sf.briar.protocol;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Random;
import junit.framework.TestCase;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Tags;
import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.SerialModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class AckReaderTest extends TestCase {
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final Mockery context;
public AckReaderTest() throws Exception {
super();
Injector i = Guice.createInjector(new SerialModule());
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
context = new Mockery();
}
@Test
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
AckFactory ackFactory = context.mock(AckFactory.class);
AckReader ackReader = new AckReader(ackFactory);
byte[] b = createAck(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.ACK, ackReader);
try {
reader.readUserDefined(Tags.ACK, Ack.class);
assertTrue(false);
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
@Test
@SuppressWarnings("unchecked")
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
final AckFactory ackFactory = context.mock(AckFactory.class);
AckReader ackReader = new AckReader(ackFactory);
final Ack ack = context.mock(Ack.class);
context.checking(new Expectations() {{
oneOf(ackFactory).createAck(with(any(Collection.class)));
will(returnValue(ack));
}});
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.ACK, ackReader);
assertEquals(ack, reader.readUserDefined(Tags.ACK, Ack.class));
context.assertIsSatisfied();
}
@Test
public void testEmptyAck() throws Exception {
final AckFactory ackFactory = context.mock(AckFactory.class);
AckReader ackReader = new AckReader(ackFactory);
final Ack ack = context.mock(Ack.class);
context.checking(new Expectations() {{
oneOf(ackFactory).createAck(
with(Collections.<BatchId>emptyList()));
will(returnValue(ack));
}});
byte[] b = createEmptyAck();
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.ACK, ackReader);
assertEquals(ack, reader.readUserDefined(Tags.ACK, Ack.class));
context.assertIsSatisfied();
}
private byte[] createAck(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(Ack.MAX_SIZE);
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.ACK);
w.writeListStart();
byte[] b = new byte[UniqueId.LENGTH];
Random random = new Random();
while(out.size() < Ack.MAX_SIZE - BatchId.SERIALISED_LENGTH) {
w.writeUserDefinedTag(Tags.BATCH_ID);
random.nextBytes(b);
w.writeRaw(b);
}
if(tooBig) {
w.writeUserDefinedTag(Tags.BATCH_ID);
random.nextBytes(b);
w.writeRaw(b);
}
w.writeListEnd();
assertEquals(tooBig, out.size() > Ack.MAX_SIZE);
return out.toByteArray();
}
private byte[] createEmptyAck() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.ACK);
w.writeListStart();
w.writeListEnd();
return out.toByteArray();
}
}

View File

@@ -1,233 +0,0 @@
package net.sf.briar.protocol;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import junit.framework.TestCase;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.Header;
import net.sf.briar.api.protocol.Tags;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.ObjectReader;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.SerialModule;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class BundleReaderImplTest extends TestCase {
private final Mockery context = new Mockery();
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
public BundleReaderImplTest() {
super();
Injector i = Guice.createInjector(new SerialModule());
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
}
@Test
public void testEmptyBundleThrowsFormatException() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[] {});
Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
try {
b.getHeader();
assertTrue(false);
} catch(FormatException expected) {}
}
@Test
public void testReadingBatchBeforeHeaderThrowsIllegalStateException()
throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(createValidBundle());
Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
try {
b.getNextBatch();
assertTrue(false);
} catch(IllegalStateException expected) {}
}
@Test
public void testMissingHeaderThrowsFormatException() throws Exception {
// Create a headless bundle
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeListStart();
w.writeUserDefinedTag(Tags.BATCH);
w.writeList(Collections.emptyList());
w.writeListEnd();
byte[] headless = out.toByteArray();
// Try to read a header from the headless bundle
ByteArrayInputStream in = new ByteArrayInputStream(headless);
Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
try {
b.getHeader();
assertTrue(false);
} catch(FormatException expected) {}
}
@Test
public void testMissingBatchListThrowsFormatException() throws Exception {
// Create a header-only bundle
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.HEADER);
w.writeList(Collections.emptyList()); // Acks
w.writeList(Collections.emptyList()); // Subs
w.writeMap(Collections.emptyMap()); // Transports
w.writeInt64(System.currentTimeMillis()); // Timestamp
byte[] headerOnly = out.toByteArray();
// Try to read a header from the header-only bundle
ByteArrayInputStream in = new ByteArrayInputStream(headerOnly);
final Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
try {
b.getHeader();
assertTrue(false);
} catch(FormatException expected) {}
}
@Test
public void testEmptyBatchListIsAcceptable() throws Exception {
// Create a bundle with no batches
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.HEADER);
w.writeList(Collections.emptyList()); // Acks
w.writeList(Collections.emptyList()); // Subs
w.writeMap(Collections.emptyMap()); // Transports
w.writeInt64(System.currentTimeMillis()); // Timestamp
w.writeListStart();
w.writeListEnd();
byte[] batchless = out.toByteArray();
// It should be possible to read the header and null
ByteArrayInputStream in = new ByteArrayInputStream(batchless);
final Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
assertNotNull(b.getHeader());
assertNull(b.getNextBatch());
}
@Test
public void testValidBundle() throws Exception {
// It should be possible to read the header, a batch, and null
ByteArrayInputStream in = new ByteArrayInputStream(createValidBundle());
final Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
assertNotNull(b.getHeader());
assertNotNull(b.getNextBatch());
assertNull(b.getNextBatch());
}
@Test
public void testReadingBatchAfterNullThrowsIllegalStateException()
throws Exception {
// Trying to read another batch after null should not succeed
ByteArrayInputStream in = new ByteArrayInputStream(createValidBundle());
final Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
assertNotNull(b.getHeader());
assertNotNull(b.getNextBatch());
assertNull(b.getNextBatch());
try {
b.getNextBatch();
assertTrue(false);
} catch(IllegalStateException expected) {}
}
@Test
public void testReadingHeaderTwiceThrowsIllegalStateException()
throws Exception {
// Trying to read the header twice should not succeed
ByteArrayInputStream in = new ByteArrayInputStream(createValidBundle());
final Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
assertNotNull(b.getHeader());
try {
b.getHeader();
assertTrue(false);
} catch(IllegalStateException expected) {}
}
@Test
public void testReadingHeaderAfterBatchThrowsIllegalStateException()
throws Exception {
// Trying to read the header after a batch should not succeed
ByteArrayInputStream in = new ByteArrayInputStream(createValidBundle());
final Reader r = readerFactory.createReader(in);
BundleReaderImpl b = new BundleReaderImpl(r, new TestHeaderReader(),
new TestBatchReader());
assertNotNull(b.getHeader());
assertNotNull(b.getNextBatch());
try {
b.getHeader();
assertTrue(false);
} catch(IllegalStateException expected) {}
}
private byte[] createValidBundle() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.HEADER);
w.writeList(Collections.emptyList()); // Acks
w.writeList(Collections.emptyList()); // Subs
w.writeMap(Collections.emptyMap()); // Transports
w.writeInt64(System.currentTimeMillis()); // Timestamp
w.writeListStart();
w.writeUserDefinedTag(Tags.BATCH);
w.writeList(Collections.emptyList()); // Messages
w.writeListEnd();
return out.toByteArray();
}
private class TestHeaderReader implements ObjectReader<Header> {
public Header readObject(Reader r) throws IOException {
r.readUserDefinedTag(Tags.HEADER);
r.readList();
r.readList();
r.readMap();
r.readInt64();
return context.mock(Header.class);
}
}
private class TestBatchReader implements ObjectReader<Batch> {
public Batch readObject(Reader r) throws IOException {
r.readUserDefinedTag(Tags.BATCH);
r.readList();
return context.mock(Batch.class);
}
}
}

View File

@@ -15,24 +15,21 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import junit.framework.TestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.AckWriter;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.BundleReader;
import net.sf.briar.api.protocol.BundleWriter;
import net.sf.briar.api.protocol.BatchWriter;
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.protocol.MessageEncoder;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.Tags;
import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.serial.Raw;
import net.sf.briar.api.serial.RawByteArray;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.WriterFactory;
@@ -45,22 +42,17 @@ import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class BundleReadWriteTest extends TestCase {
public class FileReadWriteTest extends TestCase {
private static final String SIGNATURE_ALGO = "SHA256withRSA";
private static final String KEY_PAIR_ALGO = "RSA";
private static final String DIGEST_ALGO = "SHA-256";
private final File testDir = TestUtils.getTestDirectory();
private final File bundle = new File(testDir, "bundle");
private final File file = new File(testDir, "foo");
private final long capacity = 1024L;
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 String nick = "Foo Bar";
private final String messageBody = "This is the message body! Wooooooo!";
@@ -71,7 +63,7 @@ public class BundleReadWriteTest extends TestCase {
private final KeyParser keyParser;
private final Message message;
public BundleReadWriteTest() throws Exception {
public FileReadWriteTest() throws Exception {
super();
// Inject the reader and writer factories, since they belong to
// a different component
@@ -106,40 +98,43 @@ public class BundleReadWriteTest extends TestCase {
}
@Test
public void testWriteBundle() throws Exception {
FileOutputStream out = new FileOutputStream(bundle);
BundleWriter w = new BundleWriterImpl(out, writerFactory, batchDigest,
capacity);
Raw messageRaw = new RawByteArray(message.getBytes());
public void testWriteFile() throws Exception {
FileOutputStream out = new FileOutputStream(file);
w.addHeader(acks, subs, transports);
w.addBatch(Collections.singleton(messageRaw));
w.finish();
AckWriter a = new AckWriterImpl(out, writerFactory);
a.addBatchId(ack);
a.finish();
assertTrue(bundle.exists());
assertTrue(bundle.length() > message.getSize());
BatchWriter b = new BatchWriterImpl(out, writerFactory, batchDigest);
b.addMessage(message.getBytes());
b.finish();
out.close();
assertTrue(file.exists());
assertTrue(file.length() > message.getSize());
}
@Test
public void testWriteAndReadBundle() throws Exception {
public void testWriteAndReadFile() throws Exception {
testWriteBundle();
testWriteFile();
FileInputStream in = new FileInputStream(bundle);
Reader reader = readerFactory.createReader(in);
MessageReader messageReader =
new MessageReader(keyParser, signature, messageDigest);
HeaderReader headerReader = new HeaderReader(new HeaderFactoryImpl());
AckReader ackReader = new AckReader(new AckFactoryImpl());
BatchReader batchReader = new BatchReader(batchDigest, messageReader,
new BatchFactoryImpl());
BundleReader r = new BundleReaderImpl(reader, headerReader,
batchReader);
FileInputStream in = new FileInputStream(file);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.ACK, ackReader);
reader.addObjectReader(Tags.BATCH, batchReader);
Header h = r.getHeader();
assertEquals(acks, h.getAcks());
assertEquals(subs, h.getSubscriptions());
assertEquals(transports, h.getTransports());
Batch b = r.getNextBatch();
assertTrue(reader.hasUserDefined(Tags.ACK));
Ack a = reader.readUserDefined(Tags.ACK, Ack.class);
assertEquals(Collections.singletonList(ack), a.getBatches());
assertTrue(reader.hasUserDefined(Tags.BATCH));
Batch b = reader.readUserDefined(Tags.BATCH, Batch.class);
Iterator<Message> i = b.getMessages().iterator();
assertTrue(i.hasNext());
Message m = i.next();
@@ -150,8 +145,8 @@ public class BundleReadWriteTest extends TestCase {
assertEquals(message.getTimestamp(), m.getTimestamp());
assertTrue(Arrays.equals(message.getBytes(), m.getBytes()));
assertFalse(i.hasNext());
assertNull(r.getNextBatch());
r.finish();
assertTrue(reader.eof());
}
@After

View File

@@ -1,157 +0,0 @@
package net.sf.briar.protocol;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Random;
import junit.framework.TestCase;
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.Tags;
import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.serial.FormatException;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.SerialModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class HeaderReaderTest extends TestCase {
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final Mockery context;
public HeaderReaderTest() throws Exception {
super();
Injector i = Guice.createInjector(new SerialModule());
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
context = new Mockery();
}
@Test
public void testFormatExceptionIfHeaderIsTooLarge() throws Exception {
HeaderFactory headerFactory = context.mock(HeaderFactory.class);
HeaderReader headerReader = new HeaderReader(headerFactory);
byte[] b = createHeader(Header.MAX_SIZE + 1);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.HEADER, headerReader);
try {
reader.readUserDefined(Tags.HEADER, Header.class);
assertTrue(false);
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
@Test
@SuppressWarnings("unchecked")
public void testNoFormatExceptionIfHeaderIsMaximumSize() throws Exception {
final HeaderFactory headerFactory = context.mock(HeaderFactory.class);
HeaderReader headerReader = new HeaderReader(headerFactory);
final Header header = context.mock(Header.class);
context.checking(new Expectations() {{
oneOf(headerFactory).createHeader(
with(Collections.<BatchId>emptyList()),
with(any(Collection.class)), with(any(Map.class)),
with(any(long.class)));
will(returnValue(header));
}});
byte[] b = createHeader(Header.MAX_SIZE);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.HEADER, headerReader);
assertEquals(header, reader.readUserDefined(Tags.HEADER, Header.class));
context.assertIsSatisfied();
}
@Test
public void testEmptyHeader() throws Exception {
final HeaderFactory headerFactory = context.mock(HeaderFactory.class);
HeaderReader headerReader = new HeaderReader(headerFactory);
final Header header = context.mock(Header.class);
context.checking(new Expectations() {{
oneOf(headerFactory).createHeader(
with(Collections.<BatchId>emptyList()),
with(Collections.<GroupId>emptyList()),
with(Collections.<String, String>emptyMap()),
with(any(long.class)));
will(returnValue(header));
}});
byte[] b = createEmptyHeader();
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addObjectReader(Tags.HEADER, headerReader);
assertEquals(header, reader.readUserDefined(Tags.HEADER, Header.class));
context.assertIsSatisfied();
}
private byte[] createHeader(int size) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(size);
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.HEADER);
// No acks
w.writeListStart();
w.writeListEnd();
// Fill most of the header with subs
w.writeListStart();
byte[] b = new byte[UniqueId.LENGTH];
Random random = new Random();
while(out.size() < size - 60) {
w.writeUserDefinedTag(Tags.GROUP_ID);
random.nextBytes(b);
w.writeRaw(b);
}
w.writeListEnd();
// Transports
w.writeMapStart();
w.writeString("foo");
// Build a string that will bring the header up to the expected size
int length = size - out.size() - 12;
assertTrue(length > 0);
StringBuilder s = new StringBuilder();
for(int i = 0; i < length; i++) s.append((char) ('0' + i % 10));
w.writeString(s.toString());
w.writeMapEnd();
// Timestamp
w.writeInt64(System.currentTimeMillis());
assertEquals(size, out.size());
return out.toByteArray();
}
private byte[] createEmptyHeader() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeUserDefinedTag(Tags.HEADER);
// Acks
w.writeListStart();
w.writeListEnd();
// Subs
w.writeListStart();
w.writeListEnd();
// Transports
w.writeMapStart();
w.writeMapEnd();
// Timestamp
w.writeInt64(System.currentTimeMillis());
return out.toByteArray();
}
}