mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 05:39:53 +01:00
Changed the root package from net.sf.briar to org.briarproject.
This commit is contained in:
18
briar-tests/src/org/briarproject/BriarTestCase.java
Normal file
18
briar-tests/src/org/briarproject/BriarTestCase.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.briarproject;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public abstract class BriarTestCase extends TestCase {
|
||||
|
||||
public BriarTestCase() {
|
||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||
UncaughtExceptionHandler fail = new UncaughtExceptionHandler() {
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
Thread.setDefaultUncaughtExceptionHandler(fail);
|
||||
}
|
||||
}
|
||||
161
briar-tests/src/org/briarproject/LockFairnessTest.java
Normal file
161
briar-tests/src/org/briarproject/LockFairnessTest.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package org.briarproject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class LockFairnessTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testReadersCanShareTheLock() throws Exception {
|
||||
// Use a fair lock
|
||||
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
|
||||
final CountDownLatch firstReaderHasLock = new CountDownLatch(1);
|
||||
final CountDownLatch firstReaderHasFinished = new CountDownLatch(1);
|
||||
final CountDownLatch secondReaderHasLock = new CountDownLatch(1);
|
||||
final CountDownLatch secondReaderHasFinished = new CountDownLatch(1);
|
||||
// First reader
|
||||
Thread first = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Acquire the lock
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Allow the second reader to acquire the lock
|
||||
firstReaderHasLock.countDown();
|
||||
// Wait for the second reader to acquire the lock
|
||||
assertTrue(secondReaderHasLock.await(10, SECONDS));
|
||||
} finally {
|
||||
// Release the lock
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
firstReaderHasFinished.countDown();
|
||||
}
|
||||
};
|
||||
first.start();
|
||||
// Second reader
|
||||
Thread second = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Wait for the first reader to acquire the lock
|
||||
assertTrue(firstReaderHasLock.await(10, SECONDS));
|
||||
// Acquire the lock
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Allow the first reader to release the lock
|
||||
secondReaderHasLock.countDown();
|
||||
} finally {
|
||||
// Release the lock
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
secondReaderHasFinished.countDown();
|
||||
}
|
||||
};
|
||||
second.start();
|
||||
// Wait for both readers to finish
|
||||
assertTrue(firstReaderHasFinished.await(10, SECONDS));
|
||||
assertTrue(secondReaderHasFinished.await(10, SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWritersDoNotStarve() throws Exception {
|
||||
// Use a fair lock
|
||||
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
|
||||
final CountDownLatch firstReaderHasLock = new CountDownLatch(1);
|
||||
final CountDownLatch firstReaderHasFinished = new CountDownLatch(1);
|
||||
final CountDownLatch secondReaderHasFinished = new CountDownLatch(1);
|
||||
final CountDownLatch writerHasFinished = new CountDownLatch(1);
|
||||
final AtomicBoolean secondReaderHasHeldLock = new AtomicBoolean(false);
|
||||
final AtomicBoolean writerHasHeldLock = new AtomicBoolean(false);
|
||||
// First reader
|
||||
Thread first = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Acquire the lock
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
// Allow the other threads to acquire the lock
|
||||
firstReaderHasLock.countDown();
|
||||
// Wait for both other threads to wait for the lock
|
||||
while(lock.getQueueLength() < 2) Thread.sleep(10);
|
||||
// No other thread should have acquired the lock
|
||||
assertFalse(secondReaderHasHeldLock.get());
|
||||
assertFalse(writerHasHeldLock.get());
|
||||
} finally {
|
||||
// Release the lock
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
firstReaderHasFinished.countDown();
|
||||
}
|
||||
};
|
||||
first.start();
|
||||
// Writer
|
||||
Thread writer = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Wait for the first reader to acquire the lock
|
||||
assertTrue(firstReaderHasLock.await(10, SECONDS));
|
||||
// Acquire the lock
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
writerHasHeldLock.set(true);
|
||||
// The second reader should not overtake the writer
|
||||
assertFalse(secondReaderHasHeldLock.get());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
writerHasFinished.countDown();
|
||||
}
|
||||
};
|
||||
writer.start();
|
||||
// Second reader
|
||||
Thread second = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Wait for the first reader to acquire the lock
|
||||
assertTrue(firstReaderHasLock.await(10, SECONDS));
|
||||
// Wait for the writer to wait for the lock
|
||||
while(lock.getQueueLength() < 1) Thread.sleep(10);
|
||||
// Acquire the lock
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
secondReaderHasHeldLock.set(true);
|
||||
// The second reader should not overtake the writer
|
||||
assertTrue(writerHasHeldLock.get());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
secondReaderHasFinished.countDown();
|
||||
}
|
||||
};
|
||||
second.start();
|
||||
// Wait for all the threads to finish
|
||||
assertTrue(firstReaderHasFinished.await(10, SECONDS));
|
||||
assertTrue(secondReaderHasFinished.await(10, SECONDS));
|
||||
assertTrue(writerHasFinished.await(10, SECONDS));
|
||||
}
|
||||
}
|
||||
214
briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
Normal file
214
briar-tests/src/org/briarproject/ProtocolIntegrationTest.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package org.briarproject;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.api.Author;
|
||||
import org.briarproject.api.AuthorFactory;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.messaging.Ack;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
import org.briarproject.api.messaging.GroupFactory;
|
||||
import org.briarproject.api.messaging.Message;
|
||||
import org.briarproject.api.messaging.MessageFactory;
|
||||
import org.briarproject.api.messaging.MessageId;
|
||||
import org.briarproject.api.messaging.MessageVerifier;
|
||||
import org.briarproject.api.messaging.Offer;
|
||||
import org.briarproject.api.messaging.PacketReader;
|
||||
import org.briarproject.api.messaging.PacketReaderFactory;
|
||||
import org.briarproject.api.messaging.PacketWriter;
|
||||
import org.briarproject.api.messaging.PacketWriterFactory;
|
||||
import org.briarproject.api.messaging.Request;
|
||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||
import org.briarproject.api.messaging.TransportUpdate;
|
||||
import org.briarproject.api.messaging.UnverifiedMessage;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.ConnectionReader;
|
||||
import org.briarproject.api.transport.ConnectionReaderFactory;
|
||||
import org.briarproject.api.transport.ConnectionWriter;
|
||||
import org.briarproject.api.transport.ConnectionWriterFactory;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
import org.briarproject.db.DatabaseModule;
|
||||
import org.briarproject.messaging.MessagingModule;
|
||||
import org.briarproject.messaging.duplex.DuplexMessagingModule;
|
||||
import org.briarproject.messaging.simplex.SimplexMessagingModule;
|
||||
import org.briarproject.reliability.ReliabilityModule;
|
||||
import org.briarproject.serial.SerialModule;
|
||||
import org.briarproject.system.SystemModule;
|
||||
import org.briarproject.transport.TransportModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class ProtocolIntegrationTest extends BriarTestCase {
|
||||
|
||||
private final ConnectionReaderFactory connectionReaderFactory;
|
||||
private final ConnectionWriterFactory connectionWriterFactory;
|
||||
private final PacketReaderFactory packetReaderFactory;
|
||||
private final PacketWriterFactory packetWriterFactory;
|
||||
private final MessageVerifier messageVerifier;
|
||||
|
||||
private final ContactId contactId;
|
||||
private final byte[] secret;
|
||||
private final Author author;
|
||||
private final Group group;
|
||||
private final Message message, message1;
|
||||
private final String authorName = "Alice";
|
||||
private final String contentType = "text/plain";
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
private final String messageBody = "Hello world";
|
||||
private final Collection<MessageId> messageIds;
|
||||
private final TransportId transportId;
|
||||
private final TransportProperties transportProperties;
|
||||
|
||||
public ProtocolIntegrationTest() throws Exception {
|
||||
Injector i = Guice.createInjector(new TestDatabaseModule(),
|
||||
new TestLifecycleModule(), new TestUiModule(),
|
||||
new SystemModule(), new CryptoModule(), new DatabaseModule(),
|
||||
new MessagingModule(), new DuplexMessagingModule(),
|
||||
new SimplexMessagingModule(), new ReliabilityModule(),
|
||||
new SerialModule(), new TransportModule());
|
||||
connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class);
|
||||
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
|
||||
packetReaderFactory = i.getInstance(PacketReaderFactory.class);
|
||||
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
||||
messageVerifier = i.getInstance(MessageVerifier.class);
|
||||
contactId = new ContactId(234);
|
||||
// Create a shared secret
|
||||
secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
// Create a group
|
||||
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
|
||||
group = groupFactory.createGroup("Group");
|
||||
// Create an author
|
||||
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
|
||||
CryptoComponent crypto = i.getInstance(CryptoComponent.class);
|
||||
KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
|
||||
author = authorFactory.createAuthor(authorName,
|
||||
authorKeyPair.getPublic().getEncoded());
|
||||
// Create two messages to the group: one anonymous, one pseudonymous
|
||||
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
|
||||
message = messageFactory.createAnonymousMessage(null, group,
|
||||
contentType, timestamp, messageBody.getBytes("UTF-8"));
|
||||
message1 = messageFactory.createPseudonymousMessage(null, group,
|
||||
author, authorKeyPair.getPrivate(), contentType, timestamp,
|
||||
messageBody.getBytes("UTF-8"));
|
||||
messageIds = Arrays.asList(message.getId(), message1.getId());
|
||||
// Create some transport properties
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
transportProperties = new TransportProperties(Collections.singletonMap(
|
||||
"bar", "baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndRead() throws Exception {
|
||||
read(write());
|
||||
}
|
||||
|
||||
private byte[] write() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret.clone(), 0, true);
|
||||
ConnectionWriter conn = connectionWriterFactory.createConnectionWriter(
|
||||
out, MAX_FRAME_LENGTH, Long.MAX_VALUE, ctx, false, true);
|
||||
OutputStream out1 = conn.getOutputStream();
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out1,
|
||||
false);
|
||||
|
||||
writer.writeAck(new Ack(messageIds));
|
||||
|
||||
writer.writeMessage(message.getSerialised());
|
||||
writer.writeMessage(message1.getSerialised());
|
||||
|
||||
writer.writeOffer(new Offer(messageIds));
|
||||
|
||||
writer.writeRequest(new Request(messageIds));
|
||||
|
||||
SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
|
||||
writer.writeSubscriptionUpdate(su);
|
||||
|
||||
TransportUpdate tu = new TransportUpdate(transportId,
|
||||
transportProperties, 1);
|
||||
writer.writeTransportUpdate(tu);
|
||||
|
||||
writer.flush();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private void read(byte[] connectionData) throws Exception {
|
||||
InputStream in = new ByteArrayInputStream(connectionData);
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH));
|
||||
// FIXME: Check that the expected tag was received
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret.clone(), 0, false);
|
||||
ConnectionReader conn = connectionReaderFactory.createConnectionReader(
|
||||
in, MAX_FRAME_LENGTH, ctx, true, true);
|
||||
InputStream in1 = conn.getInputStream();
|
||||
PacketReader reader = packetReaderFactory.createPacketReader(in1);
|
||||
|
||||
// Read the ack
|
||||
assertTrue(reader.hasAck());
|
||||
Ack a = reader.readAck();
|
||||
assertEquals(messageIds, a.getMessageIds());
|
||||
|
||||
// Read and verify the messages
|
||||
assertTrue(reader.hasMessage());
|
||||
UnverifiedMessage m = reader.readMessage();
|
||||
checkMessageEquality(message, messageVerifier.verifyMessage(m));
|
||||
assertTrue(reader.hasMessage());
|
||||
m = reader.readMessage();
|
||||
checkMessageEquality(message1, messageVerifier.verifyMessage(m));
|
||||
assertFalse(reader.hasMessage());
|
||||
|
||||
// Read the offer
|
||||
assertTrue(reader.hasOffer());
|
||||
Offer o = reader.readOffer();
|
||||
assertEquals(messageIds, o.getMessageIds());
|
||||
|
||||
// Read the request
|
||||
assertTrue(reader.hasRequest());
|
||||
Request req = reader.readRequest();
|
||||
assertEquals(messageIds, req.getMessageIds());
|
||||
|
||||
// Read the subscription update
|
||||
assertTrue(reader.hasSubscriptionUpdate());
|
||||
SubscriptionUpdate su = reader.readSubscriptionUpdate();
|
||||
assertEquals(Arrays.asList(group), su.getGroups());
|
||||
assertEquals(1, su.getVersion());
|
||||
|
||||
// Read the transport update
|
||||
assertTrue(reader.hasTransportUpdate());
|
||||
TransportUpdate tu = reader.readTransportUpdate();
|
||||
assertEquals(transportId, tu.getId());
|
||||
assertEquals(transportProperties, tu.getProperties());
|
||||
assertEquals(1, tu.getVersion());
|
||||
|
||||
in.close();
|
||||
}
|
||||
|
||||
private void checkMessageEquality(Message m1, Message m2) {
|
||||
assertEquals(m1.getId(), m2.getId());
|
||||
assertEquals(m1.getParent(), m2.getParent());
|
||||
assertEquals(m1.getGroup(), m2.getGroup());
|
||||
assertEquals(m1.getAuthor(), m2.getAuthor());
|
||||
assertEquals(m1.getTimestamp(), m2.getTimestamp());
|
||||
assertArrayEquals(m1.getSerialised(), m2.getSerialised());
|
||||
}
|
||||
}
|
||||
37
briar-tests/src/org/briarproject/TestDatabaseConfig.java
Normal file
37
briar-tests/src/org/briarproject/TestDatabaseConfig.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package org.briarproject;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.briarproject.api.db.DatabaseConfig;
|
||||
|
||||
public class TestDatabaseConfig implements DatabaseConfig {
|
||||
|
||||
private final File dir;
|
||||
private final long maxSize;
|
||||
private volatile byte[] key = new byte[] { 'f', 'o', 'o' };
|
||||
|
||||
public TestDatabaseConfig(File dir, long maxSize) {
|
||||
this.dir = dir;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
public boolean databaseExists() {
|
||||
return dir.isDirectory() && dir.listFiles().length > 0;
|
||||
}
|
||||
|
||||
public File getDatabaseDirectory() {
|
||||
return dir;
|
||||
}
|
||||
|
||||
public void setEncryptionKey(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getEncryptionKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public long getMaxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
}
|
||||
30
briar-tests/src/org/briarproject/TestDatabaseModule.java
Normal file
30
briar-tests/src/org/briarproject/TestDatabaseModule.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package org.briarproject;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.briarproject.api.db.DatabaseConfig;
|
||||
import org.briarproject.api.system.FileUtils;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class TestDatabaseModule extends AbstractModule {
|
||||
|
||||
private final DatabaseConfig config;
|
||||
|
||||
public TestDatabaseModule() {
|
||||
this(new File("."), Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public TestDatabaseModule(File dir) {
|
||||
this(dir, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public TestDatabaseModule(File dir, long maxSize) {
|
||||
this.config = new TestDatabaseConfig(dir, maxSize);
|
||||
}
|
||||
|
||||
protected void configure() {
|
||||
bind(DatabaseConfig.class).toInstance(config);
|
||||
bind(FileUtils.class).to(TestFileUtils.class);
|
||||
}
|
||||
}
|
||||
13
briar-tests/src/org/briarproject/TestFileUtils.java
Normal file
13
briar-tests/src/org/briarproject/TestFileUtils.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.briarproject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.briarproject.api.system.FileUtils;
|
||||
|
||||
public class TestFileUtils implements FileUtils {
|
||||
|
||||
public long getFreeSpace(File f) throws IOException {
|
||||
return f.getFreeSpace();
|
||||
}
|
||||
}
|
||||
41
briar-tests/src/org/briarproject/TestLifecycleModule.java
Normal file
41
briar-tests/src/org/briarproject/TestLifecycleModule.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package org.briarproject;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.api.lifecycle.Service;
|
||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class TestLifecycleModule extends AbstractModule {
|
||||
|
||||
protected void configure() {
|
||||
bind(LifecycleManager.class).toInstance(new LifecycleManager() {
|
||||
|
||||
public void register(Service s) {}
|
||||
|
||||
public void registerForShutdown(ExecutorService e) {}
|
||||
|
||||
public void startServices() {}
|
||||
|
||||
public void stopServices() {}
|
||||
|
||||
public void waitForDatabase() throws InterruptedException {}
|
||||
|
||||
public void waitForStartup() throws InterruptedException {}
|
||||
|
||||
public void waitForShutdown() throws InterruptedException {}
|
||||
});
|
||||
bind(ShutdownManager.class).toInstance(new ShutdownManager() {
|
||||
|
||||
public int addShutdownHook(Runnable hook) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean removeShutdownHook(int handle) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
96
briar-tests/src/org/briarproject/TestMessage.java
Normal file
96
briar-tests/src/org/briarproject/TestMessage.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package org.briarproject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.briarproject.api.Author;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
import org.briarproject.api.messaging.Message;
|
||||
import org.briarproject.api.messaging.MessageId;
|
||||
|
||||
public class TestMessage implements Message {
|
||||
|
||||
private final MessageId id, parent;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
private final String contentType, subject;
|
||||
private final long timestamp;
|
||||
private final byte[] raw;
|
||||
private final int bodyStart, bodyLength;
|
||||
|
||||
public TestMessage(MessageId id, MessageId parent, Group group,
|
||||
Author author, String contentType, String subject, long timestamp,
|
||||
byte[] raw) {
|
||||
this(id, parent, group, author, contentType, subject, timestamp, raw, 0,
|
||||
raw.length);
|
||||
}
|
||||
|
||||
public TestMessage(MessageId id, MessageId parent, Group group,
|
||||
Author author, String contentType, String subject, long timestamp,
|
||||
byte[] raw, int bodyStart, int bodyLength) {
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
this.group = group;
|
||||
this.author = author;
|
||||
this.contentType = contentType;
|
||||
this.subject = subject;
|
||||
this.timestamp = timestamp;
|
||||
this.raw = raw;
|
||||
this.bodyStart = bodyStart;
|
||||
this.bodyLength = bodyLength;
|
||||
}
|
||||
|
||||
public MessageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public MessageId getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public byte[] getSerialised() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
public int getBodyStart() {
|
||||
return bodyStart;
|
||||
}
|
||||
|
||||
public int getBodyLength() {
|
||||
return bodyLength;
|
||||
}
|
||||
|
||||
public InputStream getSerialisedStream() {
|
||||
return new ByteArrayInputStream(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof Message && id.equals(((Message)o).getId());
|
||||
}
|
||||
}
|
||||
23
briar-tests/src/org/briarproject/TestUiModule.java
Normal file
23
briar-tests/src/org/briarproject/TestUiModule.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.briarproject;
|
||||
|
||||
import org.briarproject.api.ui.UiCallback;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class TestUiModule extends AbstractModule {
|
||||
|
||||
protected void configure() {
|
||||
bind(UiCallback.class).toInstance(new UiCallback() {
|
||||
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showMessage(String... message) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
83
briar-tests/src/org/briarproject/TestUtils.java
Normal file
83
briar-tests/src/org/briarproject/TestUtils.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package org.briarproject;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.briarproject.api.UniqueId;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
private static final AtomicInteger nextTestDir =
|
||||
new AtomicInteger((int) (Math.random() * 1000 * 1000));
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static void delete(File f) {
|
||||
if(f.isDirectory()) for(File child : f.listFiles()) delete(child);
|
||||
f.delete();
|
||||
}
|
||||
|
||||
public static void createFile(File f, String s) throws IOException {
|
||||
f.getParentFile().mkdirs();
|
||||
PrintStream out = new PrintStream(new FileOutputStream(f));
|
||||
out.print(s);
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
public static File getTestDirectory() {
|
||||
int name = nextTestDir.getAndIncrement();
|
||||
File testDir = new File("test.tmp/" + name);
|
||||
return testDir;
|
||||
}
|
||||
|
||||
public static void deleteTestDirectory(File testDir) {
|
||||
delete(testDir);
|
||||
testDir.getParentFile().delete(); // Delete if empty
|
||||
}
|
||||
|
||||
public static File getBuildDirectory() {
|
||||
File build = new File("build"); // Ant
|
||||
if(build.exists() && build.isDirectory()) return build;
|
||||
File bin = new File("bin"); // Eclipse
|
||||
if(bin.exists() && bin.isDirectory()) return bin;
|
||||
throw new RuntimeException("Could not find build directory");
|
||||
}
|
||||
|
||||
public static File getFontDirectory() {
|
||||
File f = new File("i18n");
|
||||
if(f.exists() && f.isDirectory()) return f;
|
||||
f = new File("../i18n");
|
||||
if(f.exists() && f.isDirectory()) return f;
|
||||
throw new RuntimeException("Could not find font directory");
|
||||
}
|
||||
|
||||
public static byte[] getRandomId() {
|
||||
byte[] b = new byte[UniqueId.LENGTH];
|
||||
random.nextBytes(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static void readFully(InputStream in, byte[] b) throws IOException {
|
||||
int offset = 0;
|
||||
while(offset < b.length) {
|
||||
int read = in.read(b, offset, b.length - offset);
|
||||
if(read == -1) break;
|
||||
offset += read;
|
||||
}
|
||||
TestCase.assertEquals(b.length, offset);
|
||||
}
|
||||
|
||||
public static String createRandomString(int length) throws Exception {
|
||||
StringBuilder s = new StringBuilder(length);
|
||||
for(int i = 0; i < length; i++)
|
||||
s.append((char) ('a' + random.nextInt(26)));
|
||||
return s.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class KeyAgreementTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testKeyAgreement() throws Exception {
|
||||
CryptoComponent crypto = new CryptoComponentImpl();
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
byte[] aPub = aPair.getPublic().getEncoded();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
byte[] bPub = bPair.getPublic().getEncoded();
|
||||
byte[] aSecret = crypto.deriveMasterSecret(aPub, bPair, true);
|
||||
byte[] bSecret = crypto.deriveMasterSecret(bPub, aPair, false);
|
||||
assertArrayEquals(aSecret, bSecret);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class KeyDerivationTest extends BriarTestCase {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final byte[] secret;
|
||||
|
||||
public KeyDerivationTest() {
|
||||
crypto = new CryptoComponentImpl();
|
||||
secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreDistinct() {
|
||||
List<SecretKey> keys = new ArrayList<SecretKey>();
|
||||
keys.add(crypto.deriveFrameKey(secret, 0, false, false));
|
||||
keys.add(crypto.deriveFrameKey(secret, 0, false, true));
|
||||
keys.add(crypto.deriveFrameKey(secret, 0, true, false));
|
||||
keys.add(crypto.deriveFrameKey(secret, 0, true, true));
|
||||
keys.add(crypto.deriveTagKey(secret, true));
|
||||
keys.add(crypto.deriveTagKey(secret, false));
|
||||
for(int i = 0; i < 4; i++) {
|
||||
byte[] keyI = keys.get(i).getEncoded();
|
||||
for(int j = 0; j < 4; j++) {
|
||||
byte[] keyJ = keys.get(j).getEncoded();
|
||||
assertEquals(i == j, Arrays.equals(keyI, keyJ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecretAffectsDerivation() {
|
||||
Random r = new Random();
|
||||
List<byte[]> secrets = new ArrayList<byte[]>();
|
||||
for(int i = 0; i < 20; i++) {
|
||||
byte[] b = new byte[32];
|
||||
r.nextBytes(b);
|
||||
secrets.add(crypto.deriveNextSecret(b, 0));
|
||||
}
|
||||
for(int i = 0; i < 20; i++) {
|
||||
byte[] secretI = secrets.get(i);
|
||||
for(int j = 0; j < 20; j++) {
|
||||
byte[] secretJ = secrets.get(j);
|
||||
assertEquals(i == j, Arrays.equals(secretI, secretJ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionNumberAffectsDerivation() {
|
||||
List<byte[]> secrets = new ArrayList<byte[]>();
|
||||
for(int i = 0; i < 20; i++) {
|
||||
secrets.add(crypto.deriveNextSecret(secret.clone(), i));
|
||||
}
|
||||
for(int i = 0; i < 20; i++) {
|
||||
byte[] secretI = secrets.get(i);
|
||||
for(int j = 0; j < 20; j++) {
|
||||
byte[] secretJ = secrets.get(j);
|
||||
assertEquals(i == j, Arrays.equals(secretI, secretJ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class KeyEncodingAndParsingTest extends BriarTestCase {
|
||||
|
||||
private final CryptoComponentImpl crypto = new CryptoComponentImpl();
|
||||
|
||||
@Test
|
||||
public void testAgreementPublicKeyEncodingAndParsing() throws Exception {
|
||||
KeyParser parser = crypto.getAgreementKeyParser();
|
||||
// Generate two key pairs
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
// Derive the shared secret
|
||||
PublicKey aPub = aPair.getPublic();
|
||||
byte[] secret = crypto.deriveSharedSecret(bPair.getPrivate(), aPub);
|
||||
// Encode and parse the public key - no exceptions should be thrown
|
||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
||||
// Derive the shared secret again - it should be the same
|
||||
byte[] secret1 = crypto.deriveSharedSecret(bPair.getPrivate(), aPub);
|
||||
assertArrayEquals(secret, secret1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgreementPrivateKeyEncodingAndParsing() throws Exception {
|
||||
KeyParser parser = crypto.getAgreementKeyParser();
|
||||
// Generate two key pairs
|
||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||
// Derive the shared secret
|
||||
PrivateKey bPriv = bPair.getPrivate();
|
||||
byte[] secret = crypto.deriveSharedSecret(bPriv, aPair.getPublic());
|
||||
// Encode and parse the private key - no exceptions should be thrown
|
||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
||||
// Derive the shared secret again - it should be the same
|
||||
byte[] secret1 = crypto.deriveSharedSecret(bPriv, aPair.getPublic());
|
||||
assertArrayEquals(secret, secret1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgreementKeyParserByFuzzing() throws Exception {
|
||||
KeyParser parser = crypto.getAgreementKeyParser();
|
||||
// Generate a key pair to get the proper public key length
|
||||
KeyPair p = crypto.generateAgreementKeyPair();
|
||||
int pubLength = p.getPublic().getEncoded().length;
|
||||
int privLength = p.getPrivate().getEncoded().length;
|
||||
// Parse some random byte arrays - expect GeneralSecurityException
|
||||
Random random = new Random();
|
||||
byte[] pubFuzz = new byte[pubLength];
|
||||
byte[] privFuzz = new byte[privLength];
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
random.nextBytes(pubFuzz);
|
||||
try {
|
||||
parser.parsePublicKey(pubFuzz);
|
||||
} catch(GeneralSecurityException expected) {
|
||||
} catch(Exception e) {
|
||||
fail();
|
||||
}
|
||||
random.nextBytes(privFuzz);
|
||||
try {
|
||||
parser.parsePrivateKey(privFuzz);
|
||||
} catch(GeneralSecurityException expected) {
|
||||
} catch(Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignaturePublicKeyEncodingAndParsing() throws Exception {
|
||||
KeyParser parser = crypto.getSignatureKeyParser();
|
||||
// Generate two key pairs
|
||||
KeyPair aPair = crypto.generateSignatureKeyPair();
|
||||
KeyPair bPair = crypto.generateSignatureKeyPair();
|
||||
// Derive the shared secret
|
||||
PublicKey aPub = aPair.getPublic();
|
||||
byte[] secret = crypto.deriveSharedSecret(bPair.getPrivate(), aPub);
|
||||
// Encode and parse the public key - no exceptions should be thrown
|
||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
||||
// Derive the shared secret again - it should be the same
|
||||
byte[] secret1 = crypto.deriveSharedSecret(bPair.getPrivate(), aPub);
|
||||
assertArrayEquals(secret, secret1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignaturePrivateKeyEncodingAndParsing() throws Exception {
|
||||
KeyParser parser = crypto.getSignatureKeyParser();
|
||||
// Generate two key pairs
|
||||
KeyPair aPair = crypto.generateSignatureKeyPair();
|
||||
KeyPair bPair = crypto.generateSignatureKeyPair();
|
||||
// Derive the shared secret
|
||||
PrivateKey bPriv = bPair.getPrivate();
|
||||
byte[] secret = crypto.deriveSharedSecret(bPriv, aPair.getPublic());
|
||||
// Encode and parse the private key - no exceptions should be thrown
|
||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
||||
// Derive the shared secret again - it should be the same
|
||||
byte[] secret1 = crypto.deriveSharedSecret(bPriv, aPair.getPublic());
|
||||
assertArrayEquals(secret, secret1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureKeyParserByFuzzing() throws Exception {
|
||||
KeyParser parser = crypto.getSignatureKeyParser();
|
||||
// Generate a key pair to get the proper public key length
|
||||
KeyPair p = crypto.generateSignatureKeyPair();
|
||||
int pubLength = p.getPublic().getEncoded().length;
|
||||
int privLength = p.getPrivate().getEncoded().length;
|
||||
// Parse some random byte arrays - expect GeneralSecurityException
|
||||
Random random = new Random();
|
||||
byte[] pubFuzz = new byte[pubLength];
|
||||
byte[] privFuzz = new byte[privLength];
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
random.nextBytes(pubFuzz);
|
||||
try {
|
||||
parser.parsePublicKey(pubFuzz);
|
||||
} catch(GeneralSecurityException expected) {
|
||||
} catch(Exception e) {
|
||||
fail();
|
||||
}
|
||||
random.nextBytes(privFuzz);
|
||||
try {
|
||||
parser.parsePrivateKey(privFuzz);
|
||||
} catch(GeneralSecurityException expected) {
|
||||
} catch(Exception e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PasswordBasedKdfTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testEncryptionAndDecryption() {
|
||||
CryptoComponent crypto = new CryptoComponentImpl();
|
||||
Random random = new Random();
|
||||
byte[] input = new byte[1234];
|
||||
random.nextBytes(input);
|
||||
char[] password = "password".toCharArray();
|
||||
byte[] ciphertext = crypto.encryptWithPassword(input, password);
|
||||
byte[] output = crypto.decryptWithPassword(ciphertext, password);
|
||||
assertArrayEquals(input, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidCiphertextReturnsNull() {
|
||||
CryptoComponent crypto = new CryptoComponentImpl();
|
||||
Random random = new Random();
|
||||
byte[] input = new byte[1234];
|
||||
random.nextBytes(input);
|
||||
char[] password = "password".toCharArray();
|
||||
byte[] ciphertext = crypto.encryptWithPassword(input, password);
|
||||
// Modify the ciphertext
|
||||
int position = random.nextInt(ciphertext.length);
|
||||
int value = random.nextInt(256);
|
||||
ciphertext[position] = (byte) value;
|
||||
byte[] output = crypto.decryptWithPassword(ciphertext, password);
|
||||
assertNull(output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalibration() {
|
||||
CryptoComponentImpl crypto = new CryptoComponentImpl();
|
||||
// If the target time is unachievable, one iteration should be used
|
||||
int iterations = crypto.chooseIterationCount(0);
|
||||
assertEquals(1, iterations);
|
||||
// If the target time is long, more than one iteration should be used
|
||||
iterations = crypto.chooseIterationCount(10 * 1000);
|
||||
assertTrue(iterations > 1);
|
||||
// If the target time is very long, max iterations should be used
|
||||
iterations = crypto.chooseIterationCount(Integer.MAX_VALUE);
|
||||
assertEquals(Integer.MAX_VALUE, iterations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class SecretKeyImplTest extends BriarTestCase {
|
||||
|
||||
private static final int KEY_BYTES = 32; // 256 bits
|
||||
|
||||
@Test
|
||||
public void testCopiesAreErased() {
|
||||
byte[] master = new byte[KEY_BYTES];
|
||||
new Random().nextBytes(master);
|
||||
SecretKey k = new SecretKeyImpl(master);
|
||||
byte[] copy = k.getEncoded();
|
||||
assertArrayEquals(master, copy);
|
||||
k.erase();
|
||||
byte[] blank = new byte[KEY_BYTES];
|
||||
assertArrayEquals(blank, master);
|
||||
assertArrayEquals(blank, copy);
|
||||
}
|
||||
}
|
||||
346
briar-tests/src/org/briarproject/db/BasicH2Test.java
Normal file
346
briar-tests/src/org/briarproject/db/BasicH2Test.java
Normal file
@@ -0,0 +1,346 @@
|
||||
package org.briarproject.db;
|
||||
|
||||
import static java.sql.Types.BINARY;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class BasicH2Test extends BriarTestCase {
|
||||
|
||||
private static final String CREATE_TABLE =
|
||||
"CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)";
|
||||
private static final int BATCH_SIZE = 100;
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File db = new File(testDir, "db");
|
||||
private final String url = "jdbc:h2:" + db.getPath();
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
testDir.mkdirs();
|
||||
Class.forName("org.h2.Driver");
|
||||
connection = DriverManager.getConnection(url);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertUpdateAndDelete() throws Exception {
|
||||
// Create the table
|
||||
createTable(connection);
|
||||
// Generate an ID and two names
|
||||
byte[] id = new byte[32];
|
||||
new Random().nextBytes(id);
|
||||
String oldName = TestUtils.createRandomString(50);
|
||||
String newName = TestUtils.createRandomString(50);
|
||||
// Insert the ID and old name into the table
|
||||
insertRow(id, oldName);
|
||||
// Check that the old name can be retrieved using the ID
|
||||
assertTrue(rowExists(id));
|
||||
assertEquals(oldName, getName(id));
|
||||
// Update the name
|
||||
updateRow(id, newName);
|
||||
// Check that the new name can be retrieved using the ID
|
||||
assertTrue(rowExists(id));
|
||||
assertEquals(newName, getName(id));
|
||||
// Delete the row from the table
|
||||
assertTrue(deleteRow(id));
|
||||
// Check that the row no longer exists
|
||||
assertFalse(rowExists(id));
|
||||
// Deleting the row again should have no effect
|
||||
assertFalse(deleteRow(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchInsertUpdateAndDelete() throws Exception {
|
||||
// Create the table
|
||||
createTable(connection);
|
||||
// Generate some IDs and two sets of names
|
||||
byte[][] ids = new byte[BATCH_SIZE][32];
|
||||
String[] oldNames = new String[BATCH_SIZE];
|
||||
String[] newNames = new String[BATCH_SIZE];
|
||||
Random random = new Random();
|
||||
for(int i = 0; i < BATCH_SIZE; i++) {
|
||||
random.nextBytes(ids[i]);
|
||||
oldNames[i] = TestUtils.createRandomString(50);
|
||||
newNames[i] = TestUtils.createRandomString(50);
|
||||
}
|
||||
// Insert the IDs and old names into the table as a batch
|
||||
insertBatch(ids, oldNames);
|
||||
// Update the names as a batch
|
||||
updateBatch(ids, newNames);
|
||||
// Check that the new names can be retrieved using the IDs
|
||||
for(int i = 0; i < BATCH_SIZE; i++) {
|
||||
assertTrue(rowExists(ids[i]));
|
||||
assertEquals(newNames[i], getName(ids[i]));
|
||||
}
|
||||
// Delete the rows as a batch
|
||||
boolean[] deleted = deleteBatch(ids);
|
||||
// Check that the rows no longer exist
|
||||
for(int i = 0; i < BATCH_SIZE; i++) {
|
||||
assertTrue(deleted[i]);
|
||||
assertFalse(rowExists(ids[i]));
|
||||
}
|
||||
// Deleting the rows again should have no effect
|
||||
deleted = deleteBatch(ids);
|
||||
for(int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortOrder() throws Exception {
|
||||
byte[] first = new byte[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, -128
|
||||
};
|
||||
byte[] second = new byte[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
byte[] third = new byte[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 127
|
||||
};
|
||||
// Create the table
|
||||
createTable(connection);
|
||||
// Insert the rows
|
||||
insertRow(first, "first");
|
||||
insertRow(second, "second");
|
||||
insertRow(third, "third");
|
||||
insertRow(null, "null");
|
||||
// Check the ordering of the < operator: the null ID is not comparable
|
||||
assertNull(getPredecessor(first));
|
||||
assertEquals("first", getPredecessor(second));
|
||||
assertEquals("second", getPredecessor(third));
|
||||
assertNull(getPredecessor(null));
|
||||
// Check the ordering of ORDER BY: nulls come first
|
||||
List<String> names = getNames();
|
||||
assertEquals(4, names.size());
|
||||
assertEquals("null", names.get(0));
|
||||
assertEquals("first", names.get(1));
|
||||
assertEquals("second", names.get(2));
|
||||
assertEquals("third", names.get(3));
|
||||
}
|
||||
|
||||
private void createTable(Connection connection) throws SQLException {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.executeUpdate(CREATE_TABLE);
|
||||
s.close();
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void insertRow(byte[] id, String name) throws SQLException {
|
||||
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
if(id == null) ps.setNull(1, BINARY);
|
||||
else ps.setBytes(1, id);
|
||||
ps.setString(2, name);
|
||||
int affected = ps.executeUpdate();
|
||||
assertEquals(1, affected);
|
||||
ps.close();
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean rowExists(byte[] id) throws SQLException {
|
||||
assertNotNull(id);
|
||||
String sql = "SELECT NULL FROM foo WHERE uniqueID = ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, id);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
boolean found = rs.next();
|
||||
assertFalse(rs.next());
|
||||
rs.close();
|
||||
ps.close();
|
||||
return found;
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String getName(byte[] id) throws SQLException {
|
||||
assertNotNull(id);
|
||||
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, id);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
assertTrue(rs.next());
|
||||
String name = rs.getString(1);
|
||||
assertFalse(rs.next());
|
||||
rs.close();
|
||||
ps.close();
|
||||
return name;
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRow(byte[] id, String name) throws SQLException {
|
||||
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
if(id == null) ps.setNull(2, BINARY);
|
||||
else ps.setBytes(2, id);
|
||||
ps.setString(1, name);
|
||||
assertEquals(1, ps.executeUpdate());
|
||||
ps.close();
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deleteRow(byte[] id) throws SQLException {
|
||||
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
if(id == null) ps.setNull(1, BINARY);
|
||||
else ps.setBytes(1, id);
|
||||
int affected = ps.executeUpdate();
|
||||
ps.close();
|
||||
return affected == 1;
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void insertBatch(byte[][] ids, String[] names) throws SQLException {
|
||||
assertEquals(ids.length, names.length);
|
||||
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
for(int i = 0; i < ids.length; i++) {
|
||||
if(ids[i] == null) ps.setNull(1, BINARY);
|
||||
else ps.setBytes(1, ids[i]);
|
||||
ps.setString(2, names[i]);
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
assertEquals(ids.length, batchAffected.length);
|
||||
for(int i = 0; i < batchAffected.length; i++) {
|
||||
assertEquals(1, batchAffected[i]);
|
||||
}
|
||||
ps.close();
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBatch(byte[][] ids, String[] names) throws SQLException {
|
||||
assertEquals(ids.length, names.length);
|
||||
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
for(int i = 0; i < ids.length; i++) {
|
||||
if(ids[i] == null) ps.setNull(2, BINARY);
|
||||
else ps.setBytes(2, ids[i]);
|
||||
ps.setString(1, names[i]);
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
assertEquals(ids.length, batchAffected.length);
|
||||
for(int i = 0; i < batchAffected.length; i++)
|
||||
assertEquals(1, batchAffected[i]);
|
||||
ps.close();
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean[] deleteBatch(byte[][] ids) throws SQLException {
|
||||
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
for(int i = 0; i < ids.length; i++) {
|
||||
if(ids[i] == null) ps.setNull(1, BINARY);
|
||||
else ps.setBytes(1, ids[i]);
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
assertEquals(ids.length, batchAffected.length);
|
||||
boolean[] ret = new boolean[ids.length];
|
||||
for(int i = 0; i < batchAffected.length; i++)
|
||||
ret[i] = batchAffected[i] == 1;
|
||||
ps.close();
|
||||
return ret;
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String getPredecessor(byte[] id) throws SQLException {
|
||||
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
|
||||
+ " ORDER BY uniqueId DESC LIMIT ?";
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, id);
|
||||
ps.setInt(2, 1);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
String name = rs.next() ? rs.getString(1) : null;
|
||||
assertFalse(rs.next());
|
||||
rs.close();
|
||||
ps.close();
|
||||
return name;
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getNames() throws SQLException {
|
||||
String sql = "SELECT name FROM foo ORDER BY uniqueId";
|
||||
List<String> names = new ArrayList<String>();
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while(rs.next()) names.add(rs.getString(1));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return names;
|
||||
} catch(SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if(connection != null) connection.close();
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.briarproject.db;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.system.SystemTimer;
|
||||
import org.briarproject.api.system.Timer;
|
||||
import org.briarproject.db.DatabaseCleaner.Callback;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
// FIXME: Use a mock timer
|
||||
public class DatabaseCleanerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testCleanerRunsPeriodically() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(5);
|
||||
Callback callback = new Callback() {
|
||||
|
||||
public void checkFreeSpaceAndClean() throws DbException {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public boolean shouldCheckFreeSpace() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Timer timer = new SystemTimer();
|
||||
DatabaseCleanerImpl cleaner = new DatabaseCleanerImpl(timer);
|
||||
// Start the cleaner
|
||||
cleaner.startCleaning(callback, 10);
|
||||
// The database should be cleaned five times (allow 5s for system load)
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
// Stop the cleaner
|
||||
cleaner.stopCleaning();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoppingCleanerWakesItUp() throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Callback callback = new Callback() {
|
||||
|
||||
public void checkFreeSpaceAndClean() throws DbException {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public boolean shouldCheckFreeSpace() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Timer timer = new SystemTimer();
|
||||
DatabaseCleanerImpl cleaner = new DatabaseCleanerImpl(timer);
|
||||
long start = System.currentTimeMillis();
|
||||
// Start the cleaner
|
||||
cleaner.startCleaning(callback, 10 * 1000);
|
||||
// The database should be cleaned once at startup
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
// Stop the cleaner (it should be waiting between sweeps)
|
||||
cleaner.stopCleaning();
|
||||
long end = System.currentTimeMillis();
|
||||
// Check that much less than 10 seconds expired
|
||||
assertTrue(end - start < 10 * 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.briarproject.db;
|
||||
|
||||
import static org.briarproject.db.DatabaseConstants.BYTES_PER_SWEEP;
|
||||
import static org.briarproject.db.DatabaseConstants.MIN_FREE_SPACE;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
import org.briarproject.db.DatabaseCleaner.Callback;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests that use the DatabaseCleaner.Callback interface of
|
||||
* DatabaseComponentImpl.
|
||||
*/
|
||||
public class DatabaseComponentImplTest extends DatabaseComponentTest {
|
||||
|
||||
@Test
|
||||
public void testNotCleanedIfEnoughFreeSpace() throws DbException {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).getFreeSpace();
|
||||
will(returnValue(MIN_FREE_SPACE));
|
||||
}});
|
||||
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
|
||||
|
||||
db.checkFreeSpaceAndClean();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanedIfNotEnoughFreeSpace() throws DbException {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).getFreeSpace();
|
||||
will(returnValue(MIN_FREE_SPACE - 1));
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// As if by magic, some free space has appeared
|
||||
oneOf(database).getFreeSpace();
|
||||
will(returnValue(MIN_FREE_SPACE));
|
||||
}});
|
||||
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
|
||||
|
||||
db.checkFreeSpaceAndClean();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> DatabaseComponent createDatabaseComponent(
|
||||
Database<T> database, DatabaseCleaner cleaner,
|
||||
ShutdownManager shutdown) {
|
||||
return createDatabaseComponentImpl(database, cleaner, shutdown);
|
||||
}
|
||||
|
||||
private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
|
||||
Database<T> database, DatabaseCleaner cleaner,
|
||||
ShutdownManager shutdown) {
|
||||
return new DatabaseComponentImpl<T>(database, cleaner, shutdown,
|
||||
new SystemClock());
|
||||
}
|
||||
}
|
||||
1497
briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
Normal file
1497
briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
package org.briarproject.db;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ExponentialBackoffTest extends BriarTestCase {
|
||||
|
||||
private static final int ONE_HOUR = 60 * 60 * 1000;
|
||||
|
||||
@Test
|
||||
public void testFirstIntervalEqualsRoundTripTime() {
|
||||
long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
|
||||
assertEquals(2 * ONE_HOUR, first);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntervalsIncreaseExponentially() {
|
||||
long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
|
||||
long second = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 1);
|
||||
long third = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 2);
|
||||
long fourth = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 3);
|
||||
assertEquals(third, fourth / 2);
|
||||
assertEquals(second, third / 2);
|
||||
assertEquals(first, second / 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntervalIsAddedToCurrentTime() {
|
||||
long now = System.currentTimeMillis();
|
||||
long fromZero = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
|
||||
long fromNow = ExponentialBackoff.calculateExpiry(now, ONE_HOUR, 0);
|
||||
assertEquals(now, fromNow - fromZero);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoundTripTimeOverflow() {
|
||||
long maxLatency = Long.MAX_VALUE / 2 + 1; // RTT will overflow
|
||||
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
|
||||
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransmissionCountOverflow() {
|
||||
long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
|
||||
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
|
||||
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
|
||||
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 1);
|
||||
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
|
||||
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 2);
|
||||
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTimeOverflow() {
|
||||
long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
|
||||
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
|
||||
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
|
||||
expiry = ExponentialBackoff.calculateExpiry(1, maxLatency, 0);
|
||||
assertEquals(Long.MAX_VALUE, expiry); // No overflow
|
||||
expiry = ExponentialBackoff.calculateExpiry(2, maxLatency, 0);
|
||||
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
|
||||
}
|
||||
}
|
||||
1569
briar-tests/src/org/briarproject/db/H2DatabaseTest.java
Normal file
1569
briar-tests/src/org/briarproject/db/H2DatabaseTest.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
package org.briarproject.lifecycle;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ShutdownManagerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testAddAndRemove() {
|
||||
ShutdownManager s = createShutdownManager();
|
||||
Set<Integer> handles = new HashSet<Integer>();
|
||||
for(int i = 0; i < 100; i++) {
|
||||
int handle = s.addShutdownHook(new Runnable() {
|
||||
public void run() {}
|
||||
});
|
||||
// The handles should all be distinct
|
||||
assertTrue(handles.add(handle));
|
||||
}
|
||||
// The hooks should be removable
|
||||
for(int handle : handles) assertTrue(s.removeShutdownHook(handle));
|
||||
// The hooks should no longer be removable
|
||||
for(int handle : handles) assertFalse(s.removeShutdownHook(handle));
|
||||
}
|
||||
|
||||
protected ShutdownManager createShutdownManager() {
|
||||
return new ShutdownManagerImpl();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.briarproject.lifecycle;
|
||||
|
||||
import org.briarproject.api.lifecycle.ShutdownManager;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class WindowsShutdownManagerImplTest extends ShutdownManagerImplTest {
|
||||
|
||||
@Override
|
||||
protected ShutdownManager createShutdownManager() {
|
||||
return new WindowsShutdownManagerImpl();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManagerWaitsForHooksToRun() {
|
||||
WindowsShutdownManagerImpl s = new WindowsShutdownManagerImpl();
|
||||
SlowHook[] hooks = new SlowHook[10];
|
||||
for(int i = 0; i < hooks.length; i++) {
|
||||
hooks[i] = new SlowHook();
|
||||
s.addShutdownHook(hooks[i]);
|
||||
}
|
||||
s.runShutdownHooks();
|
||||
for(int i = 0; i < hooks.length; i++) assertTrue(hooks[i].finished);
|
||||
}
|
||||
|
||||
private static class SlowHook implements Runnable {
|
||||
|
||||
private volatile boolean finished = false;
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
finished = true;
|
||||
} catch(InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
briar-tests/src/org/briarproject/messaging/ConstantsTest.java
Normal file
220
briar-tests/src/org/briarproject/messaging/ConstantsTest.java
Normal file
@@ -0,0 +1,220 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.api.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_GROUP_NAME_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_SUBSCRIPTIONS;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.Author;
|
||||
import org.briarproject.api.AuthorFactory;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.crypto.Signature;
|
||||
import org.briarproject.api.messaging.Ack;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
import org.briarproject.api.messaging.GroupFactory;
|
||||
import org.briarproject.api.messaging.Message;
|
||||
import org.briarproject.api.messaging.MessageFactory;
|
||||
import org.briarproject.api.messaging.MessageId;
|
||||
import org.briarproject.api.messaging.Offer;
|
||||
import org.briarproject.api.messaging.PacketWriter;
|
||||
import org.briarproject.api.messaging.PacketWriterFactory;
|
||||
import org.briarproject.api.messaging.SubscriptionUpdate;
|
||||
import org.briarproject.api.messaging.TransportUpdate;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
import org.briarproject.db.DatabaseModule;
|
||||
import org.briarproject.messaging.duplex.DuplexMessagingModule;
|
||||
import org.briarproject.messaging.simplex.SimplexMessagingModule;
|
||||
import org.briarproject.serial.SerialModule;
|
||||
import org.briarproject.system.SystemModule;
|
||||
import org.briarproject.transport.TransportModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class ConstantsTest extends BriarTestCase {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final GroupFactory groupFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final MessageFactory messageFactory;
|
||||
private final PacketWriterFactory packetWriterFactory;
|
||||
|
||||
public ConstantsTest() throws Exception {
|
||||
Injector i = Guice.createInjector(new TestDatabaseModule(),
|
||||
new TestLifecycleModule(), new SystemModule(),
|
||||
new CryptoModule(), new DatabaseModule(), new MessagingModule(),
|
||||
new DuplexMessagingModule(), new SimplexMessagingModule(),
|
||||
new SerialModule(), new TransportModule());
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
groupFactory = i.getInstance(GroupFactory.class);
|
||||
authorFactory = i.getInstance(AuthorFactory.class);
|
||||
messageFactory = i.getInstance(MessageFactory.class);
|
||||
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgreementPublicKeys() throws Exception {
|
||||
// Generate 10 agreement key pairs
|
||||
for(int i = 0; i < 10; i++) {
|
||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||
// Check the length of the public key
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignaturePublicKeys() throws Exception {
|
||||
Random random = new Random();
|
||||
Signature sig = crypto.getSignature();
|
||||
// Generate 10 signature key pairs
|
||||
for(int i = 0; i < 10; i++) {
|
||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||
// Check the length of the public key
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
|
||||
// Sign some random data and check the length of the signature
|
||||
byte[] toBeSigned = new byte[1234];
|
||||
random.nextBytes(toBeSigned);
|
||||
sig.initSign(keyPair.getPrivate());
|
||||
sig.update(toBeSigned);
|
||||
byte[] signature = sig.sign();
|
||||
assertTrue("Length " + signature.length,
|
||||
signature.length <= MAX_SIGNATURE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageIdsFitIntoLargeAck() throws Exception {
|
||||
testMessageIdsFitIntoAck(MAX_PACKET_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageIdsFitIntoSmallAck() throws Exception {
|
||||
testMessageIdsFitIntoAck(1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageFitsIntoPacket() throws Exception {
|
||||
MessageId parent = new MessageId(TestUtils.getRandomId());
|
||||
// Create a maximum-length group
|
||||
String groupName = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
|
||||
Group group = groupFactory.createGroup(groupName);
|
||||
// Create a maximum-length author
|
||||
String authorName =
|
||||
TestUtils.createRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
|
||||
Author author = authorFactory.createAuthor(authorName, authorPublic);
|
||||
// Create a maximum-length message
|
||||
PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
|
||||
String contentType =
|
||||
TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH);
|
||||
long timestamp = Long.MAX_VALUE;
|
||||
byte[] body = new byte[MAX_BODY_LENGTH];
|
||||
Message message = messageFactory.createPseudonymousMessage(parent,
|
||||
group, author, privateKey, contentType, timestamp, body);
|
||||
// Check the size of the serialised message
|
||||
int length = message.getSerialised().length;
|
||||
assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH
|
||||
+ MAX_PUBLIC_KEY_LENGTH + MAX_AUTHOR_NAME_LENGTH
|
||||
+ MAX_PUBLIC_KEY_LENGTH + MAX_CONTENT_TYPE_LENGTH
|
||||
+ MAX_BODY_LENGTH);
|
||||
assertTrue(length <= MAX_PACKET_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageIdsFitIntoLargeOffer() throws Exception {
|
||||
testMessageIdsFitIntoOffer(MAX_PACKET_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageIdsFitIntoSmallOffer() throws Exception {
|
||||
testMessageIdsFitIntoOffer(1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesFitIntoTransportUpdate() throws Exception {
|
||||
// Create the maximum number of properties with the maximum length
|
||||
TransportProperties p = new TransportProperties();
|
||||
for(int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT; i++) {
|
||||
String key = TestUtils.createRandomString(MAX_PROPERTY_LENGTH);
|
||||
String value = TestUtils.createRandomString(MAX_PROPERTY_LENGTH);
|
||||
p.put(key, value);
|
||||
}
|
||||
// Create a maximum-length transport update
|
||||
TransportId id = new TransportId(TestUtils.getRandomId());
|
||||
TransportUpdate u = new TransportUpdate(id, p, Long.MAX_VALUE);
|
||||
// Serialise the update
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
|
||||
writer.writeTransportUpdate(u);
|
||||
// Check the size of the serialised transport update
|
||||
assertTrue(out.size() <= MAX_PACKET_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupsFitIntoSubscriptionUpdate() throws Exception {
|
||||
// Create the maximum number of maximum-length groups
|
||||
Collection<Group> groups = new ArrayList<Group>();
|
||||
for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
|
||||
String name = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
|
||||
groups.add(groupFactory.createGroup(name));
|
||||
}
|
||||
// Create a maximum-length subscription update
|
||||
SubscriptionUpdate u = new SubscriptionUpdate(groups, Long.MAX_VALUE);
|
||||
// Serialise the update
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
|
||||
writer.writeSubscriptionUpdate(u);
|
||||
// Check the size of the serialised subscription update
|
||||
assertTrue(out.size() <= MAX_PACKET_LENGTH);
|
||||
}
|
||||
|
||||
private void testMessageIdsFitIntoAck(int length) throws Exception {
|
||||
// Create an ack with as many message IDs as possible
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
|
||||
int maxMessages = writer.getMaxMessagesForRequest(length);
|
||||
Collection<MessageId> acked = new ArrayList<MessageId>();
|
||||
for(int i = 0; i < maxMessages; i++)
|
||||
acked.add(new MessageId(TestUtils.getRandomId()));
|
||||
writer.writeAck(new Ack(acked));
|
||||
// Check the size of the serialised ack
|
||||
assertTrue(out.size() <= length);
|
||||
}
|
||||
|
||||
private void testMessageIdsFitIntoOffer(int length) throws Exception {
|
||||
// Create an offer with as many message IDs as possible
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
|
||||
PacketWriter writer = packetWriterFactory.createPacketWriter(out, true);
|
||||
int maxMessages = writer.getMaxMessagesForOffer(length);
|
||||
Collection<MessageId> offered = new ArrayList<MessageId>();
|
||||
for(int i = 0; i < maxMessages; i++)
|
||||
offered.add(new MessageId(TestUtils.getRandomId()));
|
||||
writer.writeOffer(new Offer(offered));
|
||||
// Check the size of the serialised offer
|
||||
assertTrue(out.size() <= length);
|
||||
}
|
||||
}
|
||||
105
briar-tests/src/org/briarproject/messaging/ConsumersTest.java
Normal file
105
briar-tests/src/org/briarproject/messaging/ConsumersTest.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.crypto.MessageDigest;
|
||||
import org.briarproject.api.serial.CopyingConsumer;
|
||||
import org.briarproject.api.serial.CountingConsumer;
|
||||
import org.briarproject.api.serial.DigestingConsumer;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConsumersTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testDigestingConsumer() throws Exception {
|
||||
byte[] data = new byte[1234];
|
||||
// Generate some random data and digest it
|
||||
new Random().nextBytes(data);
|
||||
MessageDigest messageDigest = new TestMessageDigest();
|
||||
messageDigest.update(data);
|
||||
byte[] dig = messageDigest.digest();
|
||||
// Check that feeding a DigestingConsumer generates the same digest
|
||||
DigestingConsumer dc = new DigestingConsumer(messageDigest);
|
||||
dc.write(data[0]);
|
||||
dc.write(data, 1, data.length - 2);
|
||||
dc.write(data[data.length - 1]);
|
||||
byte[] dig1 = messageDigest.digest();
|
||||
assertArrayEquals(dig, dig1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountingConsumer() throws Exception {
|
||||
byte[] data = new byte[1234];
|
||||
CountingConsumer cc = new CountingConsumer(data.length);
|
||||
cc.write(data[0]);
|
||||
cc.write(data, 1, data.length - 2);
|
||||
cc.write(data[data.length - 1]);
|
||||
assertEquals(data.length, cc.getCount());
|
||||
try {
|
||||
cc.write((byte) 0);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyingConsumer() throws Exception {
|
||||
byte[] data = new byte[1234];
|
||||
new Random().nextBytes(data);
|
||||
// Check that a CopyingConsumer creates a faithful copy
|
||||
CopyingConsumer cc = new CopyingConsumer();
|
||||
cc.write(data[0]);
|
||||
cc.write(data, 1, data.length - 2);
|
||||
cc.write(data[data.length - 1]);
|
||||
assertArrayEquals(data, cc.getCopy());
|
||||
}
|
||||
|
||||
private static class TestMessageDigest implements MessageDigest {
|
||||
|
||||
private final java.security.MessageDigest delegate;
|
||||
|
||||
private TestMessageDigest() throws GeneralSecurityException {
|
||||
delegate = java.security.MessageDigest.getInstance("SHA-384");
|
||||
}
|
||||
|
||||
public byte[] digest() {
|
||||
return delegate.digest();
|
||||
}
|
||||
|
||||
public byte[] digest(byte[] input) {
|
||||
return delegate.digest(input);
|
||||
}
|
||||
|
||||
public int digest(byte[] buf, int offset, int len) {
|
||||
byte[] digest = digest();
|
||||
len = Math.min(len, digest.length);
|
||||
System.arraycopy(digest, 0, buf, offset, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
public int getDigestLength() {
|
||||
return delegate.getDigestLength();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
delegate.reset();
|
||||
}
|
||||
|
||||
public void update(byte input) {
|
||||
delegate.update(input);
|
||||
}
|
||||
|
||||
public void update(byte[] input) {
|
||||
delegate.update(input);
|
||||
}
|
||||
|
||||
public void update(byte[] input, int offset, int len) {
|
||||
delegate.update(input, offset, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.briarproject.messaging;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
|
||||
import static org.briarproject.api.messaging.Types.ACK;
|
||||
import static org.briarproject.api.messaging.Types.OFFER;
|
||||
import static org.briarproject.api.messaging.Types.REQUEST;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.serial.ReaderFactory;
|
||||
import org.briarproject.api.serial.SerialComponent;
|
||||
import org.briarproject.api.serial.Writer;
|
||||
import org.briarproject.api.serial.WriterFactory;
|
||||
import org.briarproject.serial.SerialModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class PacketReaderImplTest extends BriarTestCase {
|
||||
|
||||
// FIXME: This is an integration test, not a unit test
|
||||
|
||||
private final SerialComponent serial;
|
||||
private final ReaderFactory readerFactory;
|
||||
private final WriterFactory writerFactory;
|
||||
|
||||
public PacketReaderImplTest() throws Exception {
|
||||
Injector i = Guice.createInjector(new SerialModule());
|
||||
serial = i.getInstance(SerialComponent.class);
|
||||
readerFactory = i.getInstance(ReaderFactory.class);
|
||||
writerFactory = i.getInstance(WriterFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
|
||||
byte[] b = createAck(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
try {
|
||||
reader.readAck();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyAck() throws Exception {
|
||||
byte[] b = createEmptyAck();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
try {
|
||||
reader.readAck();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
|
||||
byte[] b = createOffer(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
try {
|
||||
reader.readOffer();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||
byte[] b = createOffer(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyOffer() throws Exception {
|
||||
byte[] b = createEmptyOffer();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
try {
|
||||
reader.readOffer();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
|
||||
byte[] b = createRequest(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
try {
|
||||
reader.readRequest();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||
byte[] b = createRequest(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyRequest() throws Exception {
|
||||
byte[] b = createEmptyRequest();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(readerFactory, null,
|
||||
null, in);
|
||||
try {
|
||||
reader.readRequest();
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
private byte[] createAck(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(ACK);
|
||||
w.writeListStart();
|
||||
while(out.size() + serial.getSerialisedUniqueIdLength()
|
||||
+ serial.getSerialisedListEndLength()
|
||||
+ serial.getSerialisedStructEndLength()
|
||||
< MAX_PACKET_LENGTH) {
|
||||
w.writeBytes(TestUtils.getRandomId());
|
||||
}
|
||||
if(tooBig) w.writeBytes(TestUtils.getRandomId());
|
||||
w.writeListEnd();
|
||||
w.writeStructEnd();
|
||||
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] createEmptyAck() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(ACK);
|
||||
w.writeListStart();
|
||||
w.writeListEnd();
|
||||
w.writeStructEnd();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] createOffer(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(OFFER);
|
||||
w.writeListStart();
|
||||
while(out.size() + serial.getSerialisedUniqueIdLength()
|
||||
+ serial.getSerialisedListEndLength()
|
||||
+ serial.getSerialisedStructEndLength()
|
||||
< MAX_PACKET_LENGTH) {
|
||||
w.writeBytes(TestUtils.getRandomId());
|
||||
}
|
||||
if(tooBig) w.writeBytes(TestUtils.getRandomId());
|
||||
w.writeListEnd();
|
||||
w.writeStructEnd();
|
||||
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] createEmptyOffer() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(OFFER);
|
||||
w.writeListStart();
|
||||
w.writeListEnd();
|
||||
w.writeStructEnd();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] createRequest(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(REQUEST);
|
||||
w.writeListStart();
|
||||
while(out.size() + serial.getSerialisedUniqueIdLength()
|
||||
+ serial.getSerialisedListEndLength()
|
||||
+ serial.getSerialisedStructEndLength()
|
||||
< MAX_PACKET_LENGTH) {
|
||||
w.writeBytes(TestUtils.getRandomId());
|
||||
}
|
||||
if(tooBig) w.writeBytes(TestUtils.getRandomId());
|
||||
w.writeListEnd();
|
||||
w.writeStructEnd();
|
||||
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] createEmptyRequest() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Writer w = writerFactory.createWriter(out);
|
||||
w.writeStructStart(REQUEST);
|
||||
w.writeListStart();
|
||||
w.writeListEnd();
|
||||
w.writeStructEnd();
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package org.briarproject.messaging.simplex;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DatabaseExecutor;
|
||||
import org.briarproject.api.messaging.Ack;
|
||||
import org.briarproject.api.messaging.MessageId;
|
||||
import org.briarproject.api.messaging.PacketWriterFactory;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.ConnectionRegistry;
|
||||
import org.briarproject.api.transport.ConnectionWriterFactory;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
import org.briarproject.messaging.MessagingModule;
|
||||
import org.briarproject.messaging.duplex.DuplexMessagingModule;
|
||||
import org.briarproject.serial.SerialModule;
|
||||
import org.briarproject.system.SystemModule;
|
||||
import org.briarproject.transport.TransportModule;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
|
||||
public class OutgoingSimplexConnectionTest extends BriarTestCase {
|
||||
|
||||
// FIXME: This is an integration test, not a unit test
|
||||
|
||||
private final Mockery context;
|
||||
private final DatabaseComponent db;
|
||||
private final ConnectionRegistry connRegistry;
|
||||
private final ConnectionWriterFactory connWriterFactory;
|
||||
private final PacketWriterFactory packetWriterFactory;
|
||||
private final ContactId contactId;
|
||||
private final MessageId messageId;
|
||||
private final TransportId transportId;
|
||||
private final byte[] secret;
|
||||
|
||||
public OutgoingSimplexConnectionTest() {
|
||||
context = new Mockery();
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
Module testModule = new AbstractModule() {
|
||||
public void configure() {
|
||||
bind(DatabaseComponent.class).toInstance(db);
|
||||
bind(Executor.class).annotatedWith(
|
||||
DatabaseExecutor.class).toInstance(
|
||||
Executors.newCachedThreadPool());
|
||||
}
|
||||
};
|
||||
Injector i = Guice.createInjector(testModule,
|
||||
new TestLifecycleModule(), new SystemModule(),
|
||||
new CryptoModule(), new MessagingModule(),
|
||||
new DuplexMessagingModule(), new SimplexMessagingModule(),
|
||||
new SerialModule(), new TransportModule());
|
||||
connRegistry = i.getInstance(ConnectionRegistry.class);
|
||||
connWriterFactory = i.getInstance(ConnectionWriterFactory.class);
|
||||
packetWriterFactory = i.getInstance(PacketWriterFactory.class);
|
||||
contactId = new ContactId(234);
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectionTooShort() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
|
||||
out, MAX_PACKET_LENGTH, Long.MAX_VALUE, true);
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret, 0, true);
|
||||
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
|
||||
connRegistry, connWriterFactory, packetWriterFactory, ctx,
|
||||
transport);
|
||||
connection.write();
|
||||
// Nothing should have been written
|
||||
assertEquals(0, out.size());
|
||||
// The transport should have been disposed with exception == true
|
||||
assertTrue(transport.getDisposed());
|
||||
assertTrue(transport.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNothingToSend() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
|
||||
out, MIN_CONNECTION_LENGTH, Long.MAX_VALUE, true);
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret, 0, true);
|
||||
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
|
||||
connRegistry, connWriterFactory, packetWriterFactory, ctx,
|
||||
transport);
|
||||
context.checking(new Expectations() {{
|
||||
// No transport acks to send
|
||||
oneOf(db).generateTransportAcks(contactId);
|
||||
will(returnValue(null));
|
||||
// No transport updates to send
|
||||
oneOf(db).generateTransportUpdates(with(contactId),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
// No subscription ack to send
|
||||
oneOf(db).generateSubscriptionAck(contactId);
|
||||
will(returnValue(null));
|
||||
// No subscription update to send
|
||||
oneOf(db).generateSubscriptionUpdate(with(contactId),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
// No retention ack to send
|
||||
oneOf(db).generateRetentionAck(contactId);
|
||||
will(returnValue(null));
|
||||
// No retention update to send
|
||||
oneOf(db).generateRetentionUpdate(with(contactId),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
// No acks to send
|
||||
oneOf(db).generateAck(with(contactId), with(any(int.class)));
|
||||
will(returnValue(null));
|
||||
// No messages to send
|
||||
oneOf(db).generateBatch(with(contactId), with(any(int.class)),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
connection.write();
|
||||
// Nothing should have been written
|
||||
assertEquals(0, out.size());
|
||||
// The transport should have been disposed with exception == false
|
||||
assertTrue(transport.getDisposed());
|
||||
assertFalse(transport.getException());
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSomethingToSend() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
|
||||
out, MIN_CONNECTION_LENGTH, Long.MAX_VALUE, true);
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret, 0, true);
|
||||
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
|
||||
connRegistry, connWriterFactory, packetWriterFactory, ctx,
|
||||
transport);
|
||||
final byte[] raw = new byte[1234];
|
||||
context.checking(new Expectations() {{
|
||||
// No transport acks to send
|
||||
oneOf(db).generateTransportAcks(contactId);
|
||||
will(returnValue(null));
|
||||
// No transport updates to send
|
||||
oneOf(db).generateTransportUpdates(with(contactId),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
// No subscription ack to send
|
||||
oneOf(db).generateSubscriptionAck(contactId);
|
||||
will(returnValue(null));
|
||||
// No subscription update to send
|
||||
oneOf(db).generateSubscriptionUpdate(with(contactId),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
// No retention ack to send
|
||||
oneOf(db).generateRetentionAck(contactId);
|
||||
will(returnValue(null));
|
||||
// No retention update to send
|
||||
oneOf(db).generateRetentionUpdate(with(contactId),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
// One ack to send
|
||||
oneOf(db).generateAck(with(contactId), with(any(int.class)));
|
||||
will(returnValue(new Ack(Arrays.asList(messageId))));
|
||||
// No more acks
|
||||
oneOf(db).generateAck(with(contactId), with(any(int.class)));
|
||||
will(returnValue(null));
|
||||
// One message to send
|
||||
oneOf(db).generateBatch(with(contactId), with(any(int.class)),
|
||||
with(any(long.class)));
|
||||
will(returnValue(Arrays.asList(raw)));
|
||||
// No more messages
|
||||
oneOf(db).generateBatch(with(contactId), with(any(int.class)),
|
||||
with(any(long.class)));
|
||||
will(returnValue(null));
|
||||
}});
|
||||
connection.write();
|
||||
// Something should have been written
|
||||
int overhead = TAG_LENGTH + HEADER_LENGTH + MAC_LENGTH;
|
||||
assertTrue(out.size() > overhead + UniqueId.LENGTH + raw.length);
|
||||
// The transport should have been disposed with exception == false
|
||||
assertTrue(transport.getDisposed());
|
||||
assertFalse(transport.getException());
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package org.briarproject.messaging.simplex;
|
||||
|
||||
import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.Author;
|
||||
import org.briarproject.api.AuthorId;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.LocalAuthor;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.crypto.KeyManager;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.MessageAddedEvent;
|
||||
import org.briarproject.api.messaging.Group;
|
||||
import org.briarproject.api.messaging.GroupId;
|
||||
import org.briarproject.api.messaging.Message;
|
||||
import org.briarproject.api.messaging.MessageFactory;
|
||||
import org.briarproject.api.messaging.MessageVerifier;
|
||||
import org.briarproject.api.messaging.PacketReaderFactory;
|
||||
import org.briarproject.api.messaging.PacketWriterFactory;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.ConnectionReaderFactory;
|
||||
import org.briarproject.api.transport.ConnectionRecogniser;
|
||||
import org.briarproject.api.transport.ConnectionRegistry;
|
||||
import org.briarproject.api.transport.ConnectionWriterFactory;
|
||||
import org.briarproject.api.transport.Endpoint;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
import org.briarproject.db.DatabaseModule;
|
||||
import org.briarproject.messaging.MessagingModule;
|
||||
import org.briarproject.messaging.duplex.DuplexMessagingModule;
|
||||
import org.briarproject.plugins.ImmediateExecutor;
|
||||
import org.briarproject.serial.SerialModule;
|
||||
import org.briarproject.system.SystemModule;
|
||||
import org.briarproject.transport.TransportModule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
|
||||
private static final long CLOCK_DIFFERENCE = 60 * 1000;
|
||||
private static final long LATENCY = 60 * 1000;
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File aliceDir = new File(testDir, "alice");
|
||||
private final File bobDir = new File(testDir, "bob");
|
||||
private final Group group;
|
||||
private final TransportId transportId;
|
||||
private final byte[] initialSecret;
|
||||
private final long epoch;
|
||||
|
||||
private Injector alice, bob;
|
||||
|
||||
public SimplexMessagingIntegrationTest() throws Exception {
|
||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
// Create matching secrets for Alice and Bob
|
||||
initialSecret = new byte[32];
|
||||
new Random().nextBytes(initialSecret);
|
||||
long rotationPeriod = 2 * CLOCK_DIFFERENCE + LATENCY;
|
||||
epoch = System.currentTimeMillis() - 2 * rotationPeriod;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
alice = createInjector(aliceDir);
|
||||
bob = createInjector(bobDir);
|
||||
}
|
||||
|
||||
private Injector createInjector(File dir) {
|
||||
return Guice.createInjector(new TestDatabaseModule(dir),
|
||||
new TestLifecycleModule(), new SystemModule(),
|
||||
new CryptoModule(), new DatabaseModule(), new MessagingModule(),
|
||||
new DuplexMessagingModule(), new SimplexMessagingModule(),
|
||||
new SerialModule(), new TransportModule());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInjection() {
|
||||
DatabaseComponent aliceDb = alice.getInstance(DatabaseComponent.class);
|
||||
DatabaseComponent bobDb = bob.getInstance(DatabaseComponent.class);
|
||||
assertFalse(aliceDb == bobDb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndRead() throws Exception {
|
||||
read(write());
|
||||
}
|
||||
|
||||
private byte[] write() throws Exception {
|
||||
// Open Alice's database
|
||||
DatabaseComponent db = alice.getInstance(DatabaseComponent.class);
|
||||
assertFalse(db.open());
|
||||
// Start Alice's key manager
|
||||
KeyManager km = alice.getInstance(KeyManager.class);
|
||||
km.start();
|
||||
// Add a local pseudonym for Alice
|
||||
AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
|
||||
LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
|
||||
db.addLocalAuthor(aliceAuthor);
|
||||
// Add Bob as a contact
|
||||
AuthorId bobId = new AuthorId(TestUtils.getRandomId());
|
||||
Author bobAuthor = new Author(bobId, "Bob",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
ContactId contactId = db.addContact(bobAuthor, aliceId);
|
||||
// Add the inbox group
|
||||
db.addGroup(group);
|
||||
db.setInboxGroup(contactId, group);
|
||||
// Add the transport and the endpoint
|
||||
db.addTransport(transportId, LATENCY);
|
||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
|
||||
db.addEndpoint(ep);
|
||||
km.endpointAdded(ep, LATENCY, initialSecret.clone());
|
||||
// Send Bob a message
|
||||
String contentType = "text/plain";
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] body = "Hi Bob!".getBytes("UTF-8");
|
||||
MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
|
||||
Message message = messageFactory.createAnonymousMessage(null, group,
|
||||
contentType, timestamp, body);
|
||||
db.addLocalMessage(message);
|
||||
// Create an outgoing simplex connection
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ConnectionRegistry connRegistry =
|
||||
alice.getInstance(ConnectionRegistry.class);
|
||||
ConnectionWriterFactory connWriterFactory =
|
||||
alice.getInstance(ConnectionWriterFactory.class);
|
||||
PacketWriterFactory packetWriterFactory =
|
||||
alice.getInstance(PacketWriterFactory.class);
|
||||
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
|
||||
out, Long.MAX_VALUE, Long.MAX_VALUE, false);
|
||||
ConnectionContext ctx = km.getConnectionContext(contactId, transportId);
|
||||
assertNotNull(ctx);
|
||||
OutgoingSimplexConnection simplex = new OutgoingSimplexConnection(db,
|
||||
connRegistry, connWriterFactory, packetWriterFactory, ctx,
|
||||
transport);
|
||||
// Write whatever needs to be written
|
||||
simplex.write();
|
||||
assertTrue(transport.getDisposed());
|
||||
assertFalse(transport.getException());
|
||||
// Clean up
|
||||
km.stop();
|
||||
db.close();
|
||||
// Return the contents of the simplex connection
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private void read(byte[] b) throws Exception {
|
||||
// Open Bob's database
|
||||
DatabaseComponent db = bob.getInstance(DatabaseComponent.class);
|
||||
assertFalse(db.open());
|
||||
// Start Bob's key manager
|
||||
KeyManager km = bob.getInstance(KeyManager.class);
|
||||
km.start();
|
||||
// Add a local pseudonym for Bob
|
||||
AuthorId bobId = new AuthorId(TestUtils.getRandomId());
|
||||
LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
|
||||
db.addLocalAuthor(bobAuthor);
|
||||
// Add Alice as a contact
|
||||
AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
|
||||
Author aliceAuthor = new Author(aliceId, "Alice",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
ContactId contactId = db.addContact(aliceAuthor, bobId);
|
||||
// Add the inbox group
|
||||
db.addGroup(group);
|
||||
db.setInboxGroup(contactId, group);
|
||||
// Add the transport and the endpoint
|
||||
db.addTransport(transportId, LATENCY);
|
||||
Endpoint ep = new Endpoint(contactId, transportId, epoch, false);
|
||||
db.addEndpoint(ep);
|
||||
km.endpointAdded(ep, LATENCY, initialSecret.clone());
|
||||
// Set up a database listener
|
||||
MessageListener listener = new MessageListener();
|
||||
db.addListener(listener);
|
||||
// Create a connection recogniser and recognise the connection
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
ConnectionRecogniser rec = bob.getInstance(ConnectionRecogniser.class);
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
int read = in.read(tag);
|
||||
assertEquals(tag.length, read);
|
||||
ConnectionContext ctx = rec.acceptConnection(transportId, tag);
|
||||
assertNotNull(ctx);
|
||||
// Create an incoming simplex connection
|
||||
MessageVerifier messageVerifier =
|
||||
bob.getInstance(MessageVerifier.class);
|
||||
ConnectionRegistry connRegistry =
|
||||
bob.getInstance(ConnectionRegistry.class);
|
||||
ConnectionReaderFactory connWriterFactory =
|
||||
bob.getInstance(ConnectionReaderFactory.class);
|
||||
PacketReaderFactory packetWriterFactory =
|
||||
bob.getInstance(PacketReaderFactory.class);
|
||||
TestSimplexTransportReader transport =
|
||||
new TestSimplexTransportReader(in);
|
||||
IncomingSimplexConnection simplex = new IncomingSimplexConnection(
|
||||
new ImmediateExecutor(), new ImmediateExecutor(),
|
||||
messageVerifier, db, connRegistry, connWriterFactory,
|
||||
packetWriterFactory, ctx, transport);
|
||||
// No messages should have been added yet
|
||||
assertFalse(listener.messageAdded);
|
||||
// Read whatever needs to be read
|
||||
simplex.read();
|
||||
assertTrue(transport.getDisposed());
|
||||
assertFalse(transport.getException());
|
||||
assertTrue(transport.getRecognised());
|
||||
// The private message from Alice should have been added
|
||||
assertTrue(listener.messageAdded);
|
||||
// Clean up
|
||||
km.stop();
|
||||
db.close();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private static class MessageListener implements EventListener {
|
||||
|
||||
private boolean messageAdded = false;
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if(e instanceof MessageAddedEvent) messageAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.briarproject.messaging.simplex;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
|
||||
|
||||
class TestSimplexTransportReader implements SimplexTransportReader {
|
||||
|
||||
private final InputStream in;
|
||||
|
||||
private boolean disposed = false, exception = false, recognised = false;
|
||||
|
||||
TestSimplexTransportReader(InputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return MAX_FRAME_LENGTH;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public void dispose(boolean exception, boolean recognised) {
|
||||
assert !disposed;
|
||||
disposed = true;
|
||||
this.exception = exception;
|
||||
this.recognised = recognised;
|
||||
}
|
||||
|
||||
boolean getDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
boolean getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
boolean getRecognised() {
|
||||
return recognised;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.briarproject.messaging.simplex;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
|
||||
|
||||
class TestSimplexTransportWriter implements SimplexTransportWriter {
|
||||
|
||||
private final ByteArrayOutputStream out;
|
||||
private final long capacity, maxLatency;
|
||||
private final boolean flush;
|
||||
|
||||
private boolean disposed = false, exception = false;
|
||||
|
||||
TestSimplexTransportWriter(ByteArrayOutputStream out, long capacity,
|
||||
long maxLatency, boolean flush) {
|
||||
this.out = out;
|
||||
this.capacity = capacity;
|
||||
this.maxLatency = maxLatency;
|
||||
this.flush = flush;
|
||||
}
|
||||
|
||||
public long getCapacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return MAX_FRAME_LENGTH;
|
||||
}
|
||||
|
||||
public long getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
public boolean shouldFlush() {
|
||||
return flush;
|
||||
}
|
||||
|
||||
public void dispose(boolean exception) {
|
||||
assert !disposed;
|
||||
disposed = true;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
boolean getDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
boolean getException() {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
108
briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
Normal file
108
briar-tests/src/org/briarproject/plugins/DuplexClientTest.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package org.briarproject.plugins;
|
||||
|
||||
import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.crypto.PseudoRandom;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
|
||||
public abstract class DuplexClientTest extends DuplexTest {
|
||||
|
||||
protected ClientCallback callback = null;
|
||||
|
||||
protected void run() throws IOException {
|
||||
assert plugin != null;
|
||||
// Start the plugin
|
||||
System.out.println("Starting plugin");
|
||||
if(!plugin.start()) {
|
||||
System.out.println("Plugin failed to start");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Try to connect to the server
|
||||
System.out.println("Creating connection");
|
||||
DuplexTransportConnection d = plugin.createConnection(contactId);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
return;
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
receiveChallengeSendResponse(d);
|
||||
}
|
||||
if(!plugin.supportsInvitations()) {
|
||||
System.out.println("Skipping invitation test");
|
||||
return;
|
||||
}
|
||||
// Try to create an invitation connection
|
||||
System.out.println("Creating invitation connection");
|
||||
PseudoRandom r = getPseudoRandom(123);
|
||||
d = plugin.createInvitationConnection(r, CONNECTION_TIMEOUT);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
return;
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
sendChallengeReceiveResponse(d);
|
||||
}
|
||||
} finally {
|
||||
// Stop the plugin
|
||||
System.out.println("Stopping plugin");
|
||||
plugin.stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ClientCallback implements DuplexPluginCallback {
|
||||
|
||||
private TransportConfig config = null;
|
||||
private TransportProperties local = null;
|
||||
private Map<ContactId, TransportProperties> remote = null;
|
||||
|
||||
public ClientCallback(TransportConfig config, TransportProperties local,
|
||||
Map<ContactId, TransportProperties> remote) {
|
||||
this.config = config;
|
||||
this.local = local;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
public TransportConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public TransportProperties getLocalProperties() {
|
||||
return local;
|
||||
}
|
||||
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
public void mergeConfig(TransportConfig c) {
|
||||
config = c;
|
||||
}
|
||||
|
||||
public void mergeLocalProperties(TransportProperties p) {
|
||||
local = p;
|
||||
}
|
||||
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showMessage(String... message) {}
|
||||
|
||||
public void incomingConnectionCreated(DuplexTransportConnection d) {}
|
||||
|
||||
public void outgoingConnectionCreated(ContactId contactId,
|
||||
DuplexTransportConnection d) {}
|
||||
}
|
||||
}
|
||||
111
briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
Normal file
111
briar-tests/src/org/briarproject/plugins/DuplexServerTest.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package org.briarproject.plugins;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
|
||||
public abstract class DuplexServerTest extends DuplexTest {
|
||||
|
||||
protected ServerCallback callback = null;
|
||||
|
||||
protected void run() throws Exception {
|
||||
assert callback != null;
|
||||
assert plugin != null;
|
||||
// Start the plugin
|
||||
System.out.println("Starting plugin");
|
||||
if(!plugin.start()) {
|
||||
System.out.println("Plugin failed to start");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Wait for a connection
|
||||
System.out.println("Waiting for connection");
|
||||
if(!callback.latch.await(120, SECONDS)) {
|
||||
System.out.println("No connection received");
|
||||
return;
|
||||
}
|
||||
if(!plugin.supportsInvitations()) {
|
||||
System.out.println("Skipping invitation test");
|
||||
return;
|
||||
}
|
||||
// Try to create an invitation connection
|
||||
System.out.println("Creating invitation connection");
|
||||
DuplexTransportConnection d = plugin.createInvitationConnection(
|
||||
getPseudoRandom(123), CONNECTION_TIMEOUT);
|
||||
if(d == null) {
|
||||
System.out.println("Connection failed");
|
||||
return;
|
||||
} else {
|
||||
System.out.println("Connection created");
|
||||
receiveChallengeSendResponse(d);
|
||||
}
|
||||
} finally {
|
||||
// Stop the plugin
|
||||
System.out.println("Stopping plugin");
|
||||
plugin.stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected class ServerCallback implements DuplexPluginCallback {
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
private TransportConfig config;
|
||||
private TransportProperties local;
|
||||
private Map<ContactId, TransportProperties> remote;
|
||||
|
||||
public ServerCallback(TransportConfig config, TransportProperties local,
|
||||
Map<ContactId, TransportProperties> remote) {
|
||||
this.config = config;
|
||||
this.local = local;
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
public TransportConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public TransportProperties getLocalProperties() {
|
||||
return local;
|
||||
}
|
||||
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
public void mergeConfig(TransportConfig c) {
|
||||
config = c;
|
||||
}
|
||||
|
||||
public void mergeLocalProperties(TransportProperties p) {
|
||||
local = p;
|
||||
}
|
||||
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showMessage(String... message) {}
|
||||
|
||||
public void incomingConnectionCreated(DuplexTransportConnection d) {
|
||||
System.out.println("Connection received");
|
||||
sendChallengeReceiveResponse(d);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void outgoingConnectionCreated(ContactId c,
|
||||
DuplexTransportConnection d) {}
|
||||
}
|
||||
}
|
||||
91
briar-tests/src/org/briarproject/plugins/DuplexTest.java
Normal file
91
briar-tests/src/org/briarproject/plugins/DuplexTest.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package org.briarproject.plugins;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.crypto.PseudoRandom;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
|
||||
abstract class DuplexTest {
|
||||
|
||||
protected static final String CHALLENGE = "Carrots!";
|
||||
protected static final String RESPONSE = "Potatoes!";
|
||||
|
||||
protected final ContactId contactId = new ContactId(234);
|
||||
|
||||
protected DuplexPlugin plugin = null;
|
||||
|
||||
protected void sendChallengeReceiveResponse(DuplexTransportConnection d) {
|
||||
assert plugin != null;
|
||||
try {
|
||||
PrintStream out = new PrintStream(d.getOutputStream());
|
||||
out.println(CHALLENGE);
|
||||
out.flush();
|
||||
System.out.println("Sent challenge: " + CHALLENGE);
|
||||
Scanner in = new Scanner(d.getInputStream());
|
||||
if(in.hasNextLine()) {
|
||||
String response = in.nextLine();
|
||||
System.out.println("Received response: " + response);
|
||||
if(RESPONSE.equals(response)) {
|
||||
System.out.println("Correct response");
|
||||
} else {
|
||||
System.out.println("Incorrect response");
|
||||
}
|
||||
} else {
|
||||
System.out.println("No response");
|
||||
}
|
||||
d.dispose(false, true);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
d.dispose(true, true);
|
||||
} catch(IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void receiveChallengeSendResponse(DuplexTransportConnection d) {
|
||||
assert plugin != null;
|
||||
try {
|
||||
Scanner in = new Scanner(d.getInputStream());
|
||||
if(in.hasNextLine()) {
|
||||
String challenge = in.nextLine();
|
||||
System.out.println("Received challenge: " + challenge);
|
||||
if(CHALLENGE.equals(challenge)) {
|
||||
PrintStream out = new PrintStream(d.getOutputStream());
|
||||
out.println(RESPONSE);
|
||||
out.flush();
|
||||
System.out.println("Sent response: " + RESPONSE);
|
||||
} else {
|
||||
System.out.println("Incorrect challenge");
|
||||
}
|
||||
} else {
|
||||
System.out.println("No challenge");
|
||||
}
|
||||
d.dispose(false, true);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
d.dispose(true, true);
|
||||
} catch(IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected PseudoRandom getPseudoRandom(int seed) {
|
||||
final Random random = new Random(seed);
|
||||
return new PseudoRandom() {
|
||||
public byte[] nextBytes(int bytes) {
|
||||
byte[] b = new byte[bytes];
|
||||
random.nextBytes(b);
|
||||
return b;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.briarproject.plugins;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class ImmediateExecutor implements Executor {
|
||||
|
||||
public void execute(Runnable r) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.briarproject.plugins;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPlugin;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.api.transport.ConnectionDispatcher;
|
||||
import org.briarproject.api.ui.UiCallback;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PluginManagerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testStartAndStop() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final Executor pluginExecutor = Executors.newCachedThreadPool();
|
||||
final SimplexPluginConfig simplexPluginConfig =
|
||||
context.mock(SimplexPluginConfig.class);
|
||||
final DuplexPluginConfig duplexPluginConfig =
|
||||
context.mock(DuplexPluginConfig.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Poller poller = context.mock(Poller.class);
|
||||
final ConnectionDispatcher dispatcher =
|
||||
context.mock(ConnectionDispatcher.class);
|
||||
final UiCallback uiCallback = context.mock(UiCallback.class);
|
||||
// Two simplex plugin factories: both create plugins, one fails to start
|
||||
final SimplexPluginFactory simplexFactory =
|
||||
context.mock(SimplexPluginFactory.class);
|
||||
final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||
final TransportId simplexId = new TransportId(TestUtils.getRandomId());
|
||||
final long simplexLatency = 12345;
|
||||
final SimplexPluginFactory simplexFailFactory =
|
||||
context.mock(SimplexPluginFactory.class, "simplexFailFactory");
|
||||
final SimplexPlugin simplexFailPlugin =
|
||||
context.mock(SimplexPlugin.class, "simplexFailPlugin");
|
||||
final TransportId simplexFailId =
|
||||
new TransportId(TestUtils.getRandomId());
|
||||
final long simplexFailLatency = 23456;
|
||||
// Two duplex plugin factories: one creates a plugin, the other fails
|
||||
final DuplexPluginFactory duplexFactory =
|
||||
context.mock(DuplexPluginFactory.class);
|
||||
final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
||||
final TransportId duplexId = new TransportId(TestUtils.getRandomId());
|
||||
final long duplexLatency = 34567;
|
||||
final DuplexPluginFactory duplexFailFactory =
|
||||
context.mock(DuplexPluginFactory.class, "duplexFailFactory");
|
||||
final TransportId duplexFailId =
|
||||
new TransportId(TestUtils.getRandomId());
|
||||
context.checking(new Expectations() {{
|
||||
// First simplex plugin
|
||||
oneOf(simplexPluginConfig).getFactories();
|
||||
will(returnValue(Arrays.asList(simplexFactory,
|
||||
simplexFailFactory)));
|
||||
oneOf(simplexFactory).getId();
|
||||
will(returnValue(simplexId));
|
||||
oneOf(simplexFactory).createPlugin(with(any(
|
||||
SimplexPluginCallback.class)));
|
||||
will(returnValue(simplexPlugin)); // Created
|
||||
oneOf(simplexPlugin).getMaxLatency();
|
||||
will(returnValue(simplexLatency));
|
||||
oneOf(db).addTransport(simplexId, simplexLatency);
|
||||
will(returnValue(true));
|
||||
oneOf(simplexPlugin).start();
|
||||
will(returnValue(true)); // Started
|
||||
// Second simplex plugin
|
||||
oneOf(simplexFailFactory).getId();
|
||||
will(returnValue(simplexFailId));
|
||||
oneOf(simplexFailFactory).createPlugin(with(any(
|
||||
SimplexPluginCallback.class)));
|
||||
will(returnValue(simplexFailPlugin)); // Created
|
||||
oneOf(simplexFailPlugin).getMaxLatency();
|
||||
will(returnValue(simplexFailLatency));
|
||||
oneOf(db).addTransport(simplexFailId, simplexFailLatency);
|
||||
will(returnValue(true));
|
||||
oneOf(simplexFailPlugin).start();
|
||||
will(returnValue(false)); // Failed to start
|
||||
// First duplex plugin
|
||||
oneOf(duplexPluginConfig).getFactories();
|
||||
will(returnValue(Arrays.asList(duplexFactory, duplexFailFactory)));
|
||||
oneOf(duplexFactory).getId();
|
||||
will(returnValue(duplexId));
|
||||
oneOf(duplexFactory).createPlugin(with(any(
|
||||
DuplexPluginCallback.class)));
|
||||
will(returnValue(duplexPlugin)); // Created
|
||||
oneOf(duplexPlugin).getMaxLatency();
|
||||
will(returnValue(duplexLatency));
|
||||
oneOf(db).addTransport(duplexId, duplexLatency);
|
||||
will(returnValue(true));
|
||||
oneOf(duplexPlugin).start();
|
||||
will(returnValue(true)); // Started
|
||||
// Second duplex plugin
|
||||
oneOf(duplexFailFactory).getId();
|
||||
will(returnValue(duplexFailId));
|
||||
oneOf(duplexFailFactory).createPlugin(with(any(
|
||||
DuplexPluginCallback.class)));
|
||||
will(returnValue(null)); // Failed to create a plugin
|
||||
// Start the poller
|
||||
oneOf(poller).start(Arrays.asList(simplexPlugin, duplexPlugin));
|
||||
// Stop the poller
|
||||
oneOf(poller).stop();
|
||||
// Stop the plugins
|
||||
oneOf(simplexPlugin).stop();
|
||||
oneOf(duplexPlugin).stop();
|
||||
}});
|
||||
PluginManagerImpl p = new PluginManagerImpl(pluginExecutor,
|
||||
simplexPluginConfig, duplexPluginConfig, db, poller,
|
||||
dispatcher, uiCallback);
|
||||
// Two plugins should be started and stopped
|
||||
assertTrue(p.start());
|
||||
assertTrue(p.stop());
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.briarproject.plugins.bluetooth;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
import org.briarproject.plugins.DuplexClientTest;
|
||||
|
||||
// This is not a JUnit test - it has to be run manually while the server test
|
||||
// is running on another machine
|
||||
public class BluetoothClientTest extends DuplexClientTest {
|
||||
|
||||
private BluetoothClientTest(Executor executor, String serverAddress) {
|
||||
// Store the server's Bluetooth address and UUID
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", serverAddress);
|
||||
p.put("uuid", BluetoothTest.EMPTY_UUID);
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
// Create the plugin
|
||||
callback = new ClientCallback(new TransportConfig(),
|
||||
new TransportProperties(), remote);
|
||||
plugin = new BluetoothPlugin(executor, new SystemClock(),
|
||||
new SecureRandom(), callback, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if(args.length != 1) {
|
||||
System.err.println("Please specify the server's Bluetooth address");
|
||||
System.exit(1);
|
||||
}
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
try {
|
||||
new BluetoothClientTest(executor, args[0]).run();
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.briarproject.plugins.bluetooth;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
import org.briarproject.plugins.DuplexServerTest;
|
||||
|
||||
// This is not a JUnit test - it has to be run manually while the client test
|
||||
// is running on another machine
|
||||
public class BluetoothServerTest extends DuplexServerTest {
|
||||
|
||||
private BluetoothServerTest(Executor executor) {
|
||||
// Store the UUID
|
||||
TransportProperties local = new TransportProperties();
|
||||
local.put("uuid", BluetoothTest.EMPTY_UUID);
|
||||
// Create the plugin
|
||||
callback = new ServerCallback(new TransportConfig(), local,
|
||||
Collections.singletonMap(contactId, new TransportProperties()));
|
||||
plugin = new BluetoothPlugin(executor, new SystemClock(),
|
||||
new SecureRandom(), callback, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
try {
|
||||
new BluetoothServerTest(executor).run();
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.plugins.bluetooth;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
class BluetoothTest {
|
||||
|
||||
static final String EMPTY_UUID =
|
||||
UUID.nameUUIDFromBytes(new byte[0]).toString();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.plugins.file;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class LinuxRemovableDriveFinderTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testParseMountPoint() {
|
||||
LinuxRemovableDriveFinder f = new LinuxRemovableDriveFinder();
|
||||
String line = "/dev/sda3 on / type ext3"
|
||||
+ " (rw,errors=remount-ro,commit=0)";
|
||||
assertEquals("/", f.parseMountPoint(line));
|
||||
line = "gvfs-fuse-daemon on /home/alice/.gvfs"
|
||||
+ " type fuse.gvfs-fuse-daemon (rw,nosuid,nodev,user=alice)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "fusectl on /sys/fs/fuse/connections type fusectl (rw)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "/dev/sdd1 on /media/HAZ SPACE(!) type vfat"
|
||||
+ " (rw,nosuid,nodev,uhelper=udisks,uid=1000,gid=1000,"
|
||||
+ "shortname=mixed,dmask=0077,utf8=1,showexec,flush)";
|
||||
assertEquals("/media/HAZ SPACE(!)", f.parseMountPoint(line));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.plugins.file;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MacRemovableDriveFinderTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testParseMountPoint() {
|
||||
MacRemovableDriveFinder f = new MacRemovableDriveFinder();
|
||||
String line = "/dev/disk0s3 on / (local, journaled)";
|
||||
assertEquals("/", f.parseMountPoint(line));
|
||||
line = "devfs on /dev (local)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "<volfs> on /.vol";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "automount -nsl [117] on /Network (automounted)";
|
||||
assertEquals(null, f.parseMountPoint(line)); // Can't be parsed
|
||||
line = "/dev/disk1s1 on /Volumes/HAZ SPACE(!) (local, nodev, nosuid)";
|
||||
assertEquals("/Volumes/HAZ SPACE(!)", f.parseMountPoint(line));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.briarproject.plugins.file;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.plugins.file.RemovableDriveMonitor.Callback;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PollingRemovableDriveMonitorTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testOneCallbackPerFile() throws Exception {
|
||||
// Create a finder that returns no files the first time, then two files
|
||||
final File file1 = new File("foo");
|
||||
final File file2 = new File("bar");
|
||||
final RemovableDriveFinder finder = new RemovableDriveFinder() {
|
||||
|
||||
private AtomicBoolean firstCall = new AtomicBoolean(true);
|
||||
|
||||
public Collection<File> findRemovableDrives() throws IOException {
|
||||
if(firstCall.getAndSet(false)) return Collections.emptyList();
|
||||
else return Arrays.asList(file1, file2);
|
||||
}
|
||||
};
|
||||
// Create a callback that waits for two files
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
final List<File> detected = new ArrayList<File>();
|
||||
Callback callback = new Callback() {
|
||||
|
||||
public void driveInserted(File f) {
|
||||
detected.add(f);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void exceptionThrown(IOException e) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
// Create the monitor and start it
|
||||
final RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
|
||||
Executors.newCachedThreadPool(), finder, 1);
|
||||
monitor.start(callback);
|
||||
// Wait for the monitor to detect the files
|
||||
assertTrue(latch.await(10, SECONDS));
|
||||
monitor.stop();
|
||||
// Check that both files were detected
|
||||
assertEquals(2, detected.size());
|
||||
assertTrue(detected.contains(file1));
|
||||
assertTrue(detected.contains(file2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionCallback() throws Exception {
|
||||
// Create a finder that throws an exception the second time it's polled
|
||||
final RemovableDriveFinder finder = new RemovableDriveFinder() {
|
||||
|
||||
private AtomicBoolean firstCall = new AtomicBoolean(true);
|
||||
|
||||
public Collection<File> findRemovableDrives() throws IOException {
|
||||
if(firstCall.getAndSet(false)) return Collections.emptyList();
|
||||
else throw new IOException();
|
||||
}
|
||||
};
|
||||
// Create a callback that waits for an exception
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Callback callback = new Callback() {
|
||||
|
||||
public void driveInserted(File root) {
|
||||
fail();
|
||||
}
|
||||
|
||||
public void exceptionThrown(IOException e) {
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
// Create the monitor and start it
|
||||
final RemovableDriveMonitor monitor = new PollingRemovableDriveMonitor(
|
||||
Executors.newCachedThreadPool(), finder, 1);
|
||||
monitor.start(callback);
|
||||
assertTrue(latch.await(10, SECONDS));
|
||||
monitor.stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package org.briarproject.plugins.file;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestFileUtils;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
|
||||
import org.briarproject.api.system.FileUtils;
|
||||
import org.briarproject.plugins.ImmediateExecutor;
|
||||
import org.briarproject.plugins.file.RemovableDriveMonitor.Callback;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RemovableDrivePluginTest extends BriarTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
private final FileUtils fileUtils = new TestFileUtils();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfNoDrivesAreFound() throws Exception {
|
||||
final List<File> drives = Collections.emptyList();
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfNoDriveIsChosen() throws Exception {
|
||||
final File drive1 = new File(testDir, "1");
|
||||
final File drive2 = new File(testDir, "2");
|
||||
final List<File> drives = new ArrayList<File>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String.class)));
|
||||
will(returnValue(-1)); // The user cancelled the choice
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
File[] files = drive1.listFiles();
|
||||
assertTrue(files == null || files.length == 0);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfOutputDirDoesNotExist() throws Exception {
|
||||
final File drive1 = new File(testDir, "1");
|
||||
final File drive2 = new File(testDir, "2");
|
||||
final List<File> drives = new ArrayList<File>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String.class)));
|
||||
will(returnValue(0)); // The user chose drive1 but it doesn't exist
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
File[] files = drive1.listFiles();
|
||||
assertTrue(files == null || files.length == 0);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNullIfOutputDirIsAFile() throws Exception {
|
||||
final File drive1 = new File(testDir, "1");
|
||||
final File drive2 = new File(testDir, "2");
|
||||
final List<File> drives = new ArrayList<File>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
// Create drive1 as a file rather than a directory
|
||||
assertTrue(drive1.createNewFile());
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String.class)));
|
||||
will(returnValue(0)); // The user chose drive1 but it's not a dir
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNull(plugin.createWriter(contactId));
|
||||
File[] files = drive1.listFiles();
|
||||
assertTrue(files == null || files.length == 0);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriterIsNotNullIfOutputDirIsADir() throws Exception {
|
||||
final File drive1 = new File(testDir, "1");
|
||||
final File drive2 = new File(testDir, "2");
|
||||
final List<File> drives = new ArrayList<File>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
// Create drive1 as a directory
|
||||
assertTrue(drive1.mkdir());
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String.class)));
|
||||
will(returnValue(0)); // The user chose drive1
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
assertNotNull(plugin.createWriter(contactId));
|
||||
// The output file should exist and should be empty
|
||||
File[] files = drive1.listFiles();
|
||||
assertNotNull(files);
|
||||
assertEquals(1, files.length);
|
||||
assertEquals(0, files[0].length());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWritingToWriter() throws Exception {
|
||||
final File drive1 = new File(testDir, "1");
|
||||
final File drive2 = new File(testDir, "2");
|
||||
final List<File> drives = new ArrayList<File>();
|
||||
drives.add(drive1);
|
||||
drives.add(drive2);
|
||||
// Create drive1 as a directory
|
||||
assertTrue(drive1.mkdir());
|
||||
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(finder).findRemovableDrives();
|
||||
will(returnValue(drives));
|
||||
oneOf(callback).showChoice(with(any(String[].class)),
|
||||
with(any(String.class)));
|
||||
will(returnValue(0)); // The user chose drive1
|
||||
oneOf(callback).showMessage(with(any(String.class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
SimplexTransportWriter writer = plugin.createWriter(contactId);
|
||||
assertNotNull(writer);
|
||||
// The output file should exist and should be empty
|
||||
File[] files = drive1.listFiles();
|
||||
assertNotNull(files);
|
||||
assertEquals(1, files.length);
|
||||
assertEquals(0, files[0].length());
|
||||
// Writing to the output stream should increase the size of the file
|
||||
OutputStream out = writer.getOutputStream();
|
||||
out.write(new byte[1234]);
|
||||
out.flush();
|
||||
out.close();
|
||||
// Disposing of the writer should not delete the file
|
||||
writer.dispose(false);
|
||||
assertTrue(files[0].exists());
|
||||
assertEquals(1234, files[0].length());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyDriveIsIgnored() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
plugin.driveInserted(testDir);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilenames() {
|
||||
Mockery context = new Mockery();
|
||||
final Executor executor = context.mock(Executor.class);
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(executor,
|
||||
fileUtils, callback, finder, monitor, MAX_FRAME_LENGTH, 0);
|
||||
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefg.dat"));
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefghi.dat"));
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefgh_dat"));
|
||||
assertFalse(plugin.isPossibleConnectionFilename("abcdefgh.rat"));
|
||||
assertTrue(plugin.isPossibleConnectionFilename("abcdefgh.dat"));
|
||||
assertTrue(plugin.isPossibleConnectionFilename("ABCDEFGH.DAT"));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReaderIsCreated() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final SimplexPluginCallback callback =
|
||||
context.mock(SimplexPluginCallback.class);
|
||||
final RemovableDriveFinder finder =
|
||||
context.mock(RemovableDriveFinder.class);
|
||||
final RemovableDriveMonitor monitor =
|
||||
context.mock(RemovableDriveMonitor.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(monitor).start(with(any(Callback.class)));
|
||||
oneOf(callback).readerCreated(with(any(FileTransportReader.class)));
|
||||
}});
|
||||
|
||||
RemovableDrivePlugin plugin = new RemovableDrivePlugin(
|
||||
new ImmediateExecutor(), fileUtils, callback, finder, monitor,
|
||||
MAX_FRAME_LENGTH, 0);
|
||||
plugin.start();
|
||||
|
||||
File f = new File(testDir, "abcdefgh.dat");
|
||||
OutputStream out = new FileOutputStream(f);
|
||||
out.write(new byte[MIN_CONNECTION_LENGTH]);
|
||||
out.flush();
|
||||
out.close();
|
||||
assertEquals(MIN_CONNECTION_LENGTH, f.length());
|
||||
plugin.driveInserted(testDir);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.briarproject.plugins.file;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.plugins.file.RemovableDriveMonitor.Callback;
|
||||
import org.briarproject.util.OsUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class UnixRemovableDriveMonitorTest extends BriarTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonexistentDir() throws Exception {
|
||||
if(!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) {
|
||||
System.err.println("Warning: Skipping test");
|
||||
return;
|
||||
}
|
||||
File doesNotExist = new File(testDir, "doesNotExist");
|
||||
RemovableDriveMonitor monitor = createMonitor(doesNotExist);
|
||||
monitor.start(new Callback() {
|
||||
|
||||
public void driveInserted(File root) {
|
||||
fail();
|
||||
}
|
||||
|
||||
public void exceptionThrown(IOException e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
monitor.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneCallbackPerFile() throws Exception {
|
||||
if(!(OsUtils.isLinux() || OsUtils.isMacLeopardOrNewer())) {
|
||||
System.err.println("Warning: Skipping test");
|
||||
return;
|
||||
}
|
||||
// Create a callback that will wait for two files before stopping
|
||||
final List<File> detected = new ArrayList<File>();
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
final Callback callback = new Callback() {
|
||||
|
||||
public void driveInserted(File f) {
|
||||
detected.add(f);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void exceptionThrown(IOException e) {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
// Create the monitor and start it
|
||||
RemovableDriveMonitor monitor = createMonitor(testDir);
|
||||
monitor.start(callback);
|
||||
// Create two files in the test directory
|
||||
File file1 = new File(testDir, "1");
|
||||
File file2 = new File(testDir, "2");
|
||||
assertTrue(file1.createNewFile());
|
||||
assertTrue(file2.createNewFile());
|
||||
// Wait for the monitor to detect the files
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
monitor.stop();
|
||||
// Check that both files were detected
|
||||
assertEquals(2, detected.size());
|
||||
assertTrue(detected.contains(file1));
|
||||
assertTrue(detected.contains(file2));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private RemovableDriveMonitor createMonitor(final File dir) {
|
||||
return new UnixRemovableDriveMonitor() {
|
||||
@Override
|
||||
protected String[] getPathsToWatch() {
|
||||
return new String[] { dir.getPath() };
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.briarproject.plugins.modem;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CountryCodesTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testTranslation() {
|
||||
// Unrecognised country for caller
|
||||
assertNull(CountryCodes.translate("02012345678", "ZZ", "GB"));
|
||||
// Unrecognised country for callee
|
||||
assertNull(CountryCodes.translate("02012345678", "GB", "ZZ"));
|
||||
|
||||
// GB to GB, callee has not included a prefix
|
||||
assertEquals("02012345678",
|
||||
CountryCodes.translate("2012345678", "GB", "GB"));
|
||||
// GB to GB, callee has included NDD prefix
|
||||
assertEquals("02012345678",
|
||||
CountryCodes.translate("02012345678", "GB", "GB"));
|
||||
// GB to GB, callee has included plus sign and country code
|
||||
assertEquals("02012345678",
|
||||
CountryCodes.translate("+442012345678", "GB", "GB"));
|
||||
// GB to GB, callee has included IDD prefix and country code
|
||||
assertEquals("02012345678",
|
||||
CountryCodes.translate("00442012345678", "GB", "GB"));
|
||||
|
||||
// Russia to GB, callee has not included a prefix
|
||||
assertEquals("8**10442012345678",
|
||||
CountryCodes.translate("2012345678", "RU", "GB"));
|
||||
// Russia to GB, callee has included NDD prefix
|
||||
assertEquals("8**10442012345678",
|
||||
CountryCodes.translate("02012345678", "RU", "GB"));
|
||||
// Russia to GB, callee has included plus sign and country code
|
||||
assertEquals("8**10442012345678",
|
||||
CountryCodes.translate("+442012345678", "RU", "GB"));
|
||||
// Russia to GB, callee has included IDD prefix and country code
|
||||
assertEquals("8**10442012345678",
|
||||
CountryCodes.translate("00442012345678", "RU", "GB"));
|
||||
|
||||
// Andorra to Andorra (no NDD), callee has not included a prefix
|
||||
assertEquals("765432", CountryCodes.translate("765432", "AD", "AD"));
|
||||
// Andorra to Andorra, callee has included plus sign and country code
|
||||
assertEquals("765432",
|
||||
CountryCodes.translate("+376765432", "AD", "AD"));
|
||||
// Andorra to Andorra, callee has included IDD and country code
|
||||
assertEquals("765432",
|
||||
CountryCodes.translate("00376765432", "AD", "AD"));
|
||||
|
||||
// GB to Andorra (no NDD), callee has not included a prefix
|
||||
assertEquals("00376765432",
|
||||
CountryCodes.translate("765432", "GB", "AD"));
|
||||
// GB to Andorra, callee has included plus sign and country code
|
||||
assertEquals("00376765432",
|
||||
CountryCodes.translate("+376765432", "GB", "AD"));
|
||||
// GB to Andorra, callee has included IDD and country code
|
||||
assertEquals("00376765432",
|
||||
CountryCodes.translate("00376765432", "GB", "AD"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package org.briarproject.plugins.modem;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ModemPluginTest extends BriarTestCase {
|
||||
|
||||
private static final String ISO_1336 = "GB";
|
||||
private static final String NUMBER1 = "0123";
|
||||
private static final String NUMBER2 = "0234";
|
||||
private static final String NUMBER3 = "0345";
|
||||
|
||||
@Test
|
||||
public void testModemCreation() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final ModemFactory modemFactory = context.mock(ModemFactory.class);
|
||||
final SerialPortList serialPortList =
|
||||
context.mock(SerialPortList.class);
|
||||
final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
|
||||
serialPortList, null, 0, 0, 0, true);
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo", "bar", "baz" }));
|
||||
// First call to createModem() returns false
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(false));
|
||||
// Second call to createModem() throws an exception
|
||||
oneOf(modemFactory).createModem(plugin, "bar");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(throwException(new IOException()));
|
||||
// Third call to createModem() returns true
|
||||
oneOf(modemFactory).createModem(plugin, "baz");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
}});
|
||||
assertTrue(plugin.start());
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateConnection() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final ModemFactory modemFactory = context.mock(ModemFactory.class);
|
||||
final SerialPortList serialPortList =
|
||||
context.mock(SerialPortList.class);
|
||||
final DuplexPluginCallback callback =
|
||||
context.mock(DuplexPluginCallback.class);
|
||||
final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
|
||||
serialPortList, callback, 0, 0, 0, true);
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("iso3166", ISO_1336);
|
||||
p.put("number", NUMBER1);
|
||||
ContactId contactId = new ContactId(234);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo" }));
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER1);
|
||||
will(returnValue(true));
|
||||
}});
|
||||
assertTrue(plugin.start());
|
||||
// A connection should be returned
|
||||
assertNotNull(plugin.createConnection(contactId));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateConnectionWhenDialReturnsFalse() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final ModemFactory modemFactory = context.mock(ModemFactory.class);
|
||||
final SerialPortList serialPortList =
|
||||
context.mock(SerialPortList.class);
|
||||
final DuplexPluginCallback callback =
|
||||
context.mock(DuplexPluginCallback.class);
|
||||
final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
|
||||
serialPortList, callback, 0, 0, 0, true);
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("iso3166", ISO_1336);
|
||||
p.put("number", NUMBER1);
|
||||
ContactId contactId = new ContactId(234);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo" }));
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER1);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
assertTrue(plugin.start());
|
||||
// No connection should be returned
|
||||
assertNull(plugin.createConnection(contactId));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateConnectionWhenDialThrowsException() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final ModemFactory modemFactory = context.mock(ModemFactory.class);
|
||||
final SerialPortList serialPortList =
|
||||
context.mock(SerialPortList.class);
|
||||
final DuplexPluginCallback callback =
|
||||
context.mock(DuplexPluginCallback.class);
|
||||
final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
|
||||
serialPortList, callback, 0, 0, 0, true);
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("iso3166", ISO_1336);
|
||||
p.put("number", NUMBER1);
|
||||
ContactId contactId = new ContactId(234);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo" }));
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
// createConnection()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
will(returnValue(remote));
|
||||
oneOf(modem).dial(NUMBER1);
|
||||
will(throwException(new IOException()));
|
||||
// resetModem()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo" }));
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
}});
|
||||
assertTrue(plugin.start());
|
||||
// No connection should be returned
|
||||
assertNull(plugin.createConnection(contactId));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPolling() throws Exception {
|
||||
final ExecutorService pluginExecutor =
|
||||
Executors.newSingleThreadExecutor();
|
||||
Mockery context = new Mockery();
|
||||
final ModemFactory modemFactory = context.mock(ModemFactory.class);
|
||||
final SerialPortList serialPortList =
|
||||
context.mock(SerialPortList.class);
|
||||
final DuplexPluginCallback callback =
|
||||
context.mock(DuplexPluginCallback.class);
|
||||
// Disable shuffling for this test, it confuses jMock
|
||||
final ModemPlugin plugin = new ModemPlugin(pluginExecutor, modemFactory,
|
||||
serialPortList, callback, 0, 0, 0, false);
|
||||
final Modem modem = context.mock(Modem.class);
|
||||
final TransportProperties local = new TransportProperties();
|
||||
local.put("iso3166", ISO_1336);
|
||||
TransportProperties p1 = new TransportProperties();
|
||||
p1.put("iso3166", ISO_1336);
|
||||
p1.put("number", NUMBER1);
|
||||
TransportProperties p2 = new TransportProperties();
|
||||
p2.put("iso3166", ISO_1336);
|
||||
p2.put("number", NUMBER2);
|
||||
TransportProperties p3 = new TransportProperties();
|
||||
p3.put("iso3166", ISO_1336);
|
||||
p3.put("number", NUMBER3);
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
ContactId contactId2 = new ContactId(345);
|
||||
ContactId contactId3 = new ContactId(456);
|
||||
final Map<ContactId, TransportProperties> remote =
|
||||
new HashMap<ContactId, TransportProperties>();
|
||||
remote.put(contactId1, p1);
|
||||
remote.put(contactId2, p2);
|
||||
remote.put(contactId3, p3);
|
||||
final DisposeAction disposeAction = new DisposeAction();
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo" }));
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
// poll()
|
||||
oneOf(callback).getLocalProperties();
|
||||
will(returnValue(local));
|
||||
oneOf(callback).getRemoteProperties();
|
||||
will(returnValue(remote));
|
||||
// First call to dial() throws an exception
|
||||
oneOf(modem).dial(NUMBER1);
|
||||
will(throwException(new IOException()));
|
||||
// resetModem()
|
||||
oneOf(serialPortList).getPortNames();
|
||||
will(returnValue(new String[] { "foo" }));
|
||||
oneOf(modemFactory).createModem(plugin, "foo");
|
||||
will(returnValue(modem));
|
||||
oneOf(modem).start();
|
||||
will(returnValue(true));
|
||||
// Second call to dial() returns true
|
||||
oneOf(modem).dial(NUMBER2);
|
||||
will(returnValue(true));
|
||||
// A connection is passed to the callback - dispose of it
|
||||
oneOf(callback).outgoingConnectionCreated(
|
||||
with(any(ContactId.class)),
|
||||
with(any(DuplexTransportConnection.class)));
|
||||
will(disposeAction);
|
||||
oneOf(modem).hangUp();
|
||||
// Third call to dial() returns false
|
||||
oneOf(modem).dial(NUMBER3);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
assertTrue(plugin.start());
|
||||
plugin.poll(Collections.<ContactId>emptyList());
|
||||
assertTrue(disposeAction.invoked.await(5, SECONDS));
|
||||
pluginExecutor.shutdown();
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private static class DisposeAction implements Action {
|
||||
|
||||
private final CountDownLatch invoked = new CountDownLatch(1);
|
||||
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Disposes of a transport connection");
|
||||
}
|
||||
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
DuplexTransportConnection conn =
|
||||
(DuplexTransportConnection) invocation.getParameter(1);
|
||||
conn.dispose(false, true);
|
||||
invoked.countDown();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.plugins.tcp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
import org.briarproject.plugins.DuplexClientTest;
|
||||
|
||||
// This is not a JUnit test - it has to be run manually while the server test
|
||||
// is running on another machine
|
||||
public class LanTcpClientTest extends DuplexClientTest {
|
||||
|
||||
private LanTcpClientTest(Executor executor, String serverAddress,
|
||||
String serverPort) {
|
||||
// Store the server's internal address and port
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("internal", serverAddress);
|
||||
p.put("port", serverPort);
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
Collections.singletonMap(contactId, p);
|
||||
// Create the plugin
|
||||
callback = new ClientCallback(new TransportConfig(),
|
||||
new TransportProperties(), remote);
|
||||
Clock clock = new SystemClock();
|
||||
plugin = new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if(args.length != 2) {
|
||||
System.err.println("Please specify the server's address and port");
|
||||
System.exit(1);
|
||||
}
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
try {
|
||||
new LanTcpClientTest(executor, args[0], args[1]).run();
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.briarproject.plugins.tcp;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class LanTcpPluginTest extends BriarTestCase {
|
||||
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
|
||||
@Test
|
||||
public void testIncomingConnection() throws Exception {
|
||||
Callback callback = new Callback();
|
||||
callback.local.put("address", "127.0.0.1");
|
||||
callback.local.put("port", "0");
|
||||
Executor executor = Executors.newCachedThreadPool();
|
||||
Clock clock = new SystemClock();
|
||||
DuplexPlugin plugin =
|
||||
new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
|
||||
plugin.start();
|
||||
// The plugin should have bound a socket and stored the port number
|
||||
assertTrue(callback.propertiesLatch.await(5, SECONDS));
|
||||
String host = callback.local.get("address");
|
||||
assertNotNull(host);
|
||||
assertEquals("127.0.0.1", host);
|
||||
String portString = callback.local.get("port");
|
||||
assertNotNull(portString);
|
||||
int port = Integer.parseInt(portString);
|
||||
assertTrue(port > 0 && port < 65536);
|
||||
// The plugin should be listening on the port
|
||||
InetSocketAddress addr = new InetSocketAddress(host, port);
|
||||
Socket s = new Socket();
|
||||
s.connect(addr, 100);
|
||||
assertTrue(callback.connectionsLatch.await(5, SECONDS));
|
||||
s.close();
|
||||
// Stop the plugin
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutgoingConnection() throws Exception {
|
||||
Callback callback = new Callback();
|
||||
Executor executor = Executors.newCachedThreadPool();
|
||||
Clock clock = new SystemClock();
|
||||
DuplexPlugin plugin =
|
||||
new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
|
||||
plugin.start();
|
||||
// Listen on a local port
|
||||
final ServerSocket ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress("127.0.0.1", 0), 10);
|
||||
int port = ss.getLocalPort();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean error = new AtomicBoolean(false);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ss.accept();
|
||||
latch.countDown();
|
||||
} catch(IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
// Tell the plugin about the port
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("address", "127.0.0.1");
|
||||
p.put("port", String.valueOf(port));
|
||||
callback.remote.put(contactId, p);
|
||||
// Connect to the port
|
||||
DuplexTransportConnection d = plugin.createConnection(contactId);
|
||||
assertNotNull(d);
|
||||
// Check that the connection was accepted
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
assertFalse(error.get());
|
||||
// Clean up
|
||||
d.dispose(false, true);
|
||||
ss.close();
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
private static class Callback implements DuplexPluginCallback {
|
||||
|
||||
private final Map<ContactId, TransportProperties> remote =
|
||||
new Hashtable<ContactId, TransportProperties>();
|
||||
private final CountDownLatch propertiesLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch connectionsLatch = new CountDownLatch(1);
|
||||
private final TransportProperties local = new TransportProperties();
|
||||
|
||||
public TransportConfig getConfig() {
|
||||
return new TransportConfig();
|
||||
}
|
||||
|
||||
public TransportProperties getLocalProperties() {
|
||||
return local;
|
||||
}
|
||||
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
public void mergeConfig(TransportConfig c) {}
|
||||
|
||||
public void mergeLocalProperties(TransportProperties p) {
|
||||
local.putAll(p);
|
||||
propertiesLatch.countDown();
|
||||
}
|
||||
|
||||
public int showChoice(String[] options, String... message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean showConfirmationMessage(String... message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void showMessage(String... message) {}
|
||||
|
||||
public void incomingConnectionCreated(DuplexTransportConnection d) {
|
||||
connectionsLatch.countDown();
|
||||
}
|
||||
|
||||
public void outgoingConnectionCreated(ContactId c,
|
||||
DuplexTransportConnection d) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.plugins.tcp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.briarproject.api.TransportConfig;
|
||||
import org.briarproject.api.TransportProperties;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.system.SystemClock;
|
||||
import org.briarproject.plugins.DuplexServerTest;
|
||||
|
||||
// This is not a JUnit test - it has to be run manually while the client test
|
||||
// is running on another machine
|
||||
public class LanTcpServerTest extends DuplexServerTest {
|
||||
|
||||
private LanTcpServerTest(Executor executor) {
|
||||
callback = new ServerCallback(new TransportConfig(),
|
||||
new TransportProperties(),
|
||||
Collections.singletonMap(contactId, new TransportProperties()));
|
||||
Clock clock = new SystemClock();
|
||||
plugin = new LanTcpPlugin(executor, clock, callback, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
try {
|
||||
new LanTcpServerTest(executor).run();
|
||||
} finally {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
350
briar-tests/src/org/briarproject/serial/ReaderImplTest.java
Normal file
350
briar-tests/src/org/briarproject/serial/ReaderImplTest.java
Normal file
@@ -0,0 +1,350 @@
|
||||
package org.briarproject.serial;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ReaderImplTest extends BriarTestCase {
|
||||
|
||||
private ByteArrayInputStream in = null;
|
||||
private ReaderImpl r = null;
|
||||
|
||||
@Test
|
||||
public void testReadBoolean() throws Exception {
|
||||
setContents("FF" + "FE");
|
||||
assertFalse(r.readBoolean());
|
||||
assertTrue(r.readBoolean());
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipBoolean() throws Exception {
|
||||
setContents("FF" + "FE");
|
||||
r.skipBoolean();
|
||||
r.skipBoolean();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt8() throws Exception {
|
||||
setContents("FD00" + "FDFF" + "FD7F" + "FD80");
|
||||
assertEquals((byte) 0, r.readInt8());
|
||||
assertEquals((byte) -1, r.readInt8());
|
||||
assertEquals(Byte.MAX_VALUE, r.readInt8());
|
||||
assertEquals(Byte.MIN_VALUE, r.readInt8());
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipInt8() throws Exception {
|
||||
setContents("FD00");
|
||||
r.skipInt8();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt16() throws Exception {
|
||||
setContents("FC0000" + "FCFFFF" + "FC7FFF" + "FC8000");
|
||||
assertEquals((short) 0, r.readInt16());
|
||||
assertEquals((short) -1, r.readInt16());
|
||||
assertEquals(Short.MAX_VALUE, r.readInt16());
|
||||
assertEquals(Short.MIN_VALUE, r.readInt16());
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipInt16() throws Exception {
|
||||
setContents("FC0000");
|
||||
r.skipInt16();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt32() throws Exception {
|
||||
setContents("FB00000000" + "FBFFFFFFFF" + "FB7FFFFFFF" + "FB80000000");
|
||||
assertEquals(0, r.readInt32());
|
||||
assertEquals(-1, r.readInt32());
|
||||
assertEquals(Integer.MAX_VALUE, r.readInt32());
|
||||
assertEquals(Integer.MIN_VALUE, r.readInt32());
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipInt32() throws Exception {
|
||||
setContents("FB00000000");
|
||||
r.skipInt32();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt64() throws Exception {
|
||||
setContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF"
|
||||
+ "FA7FFFFFFFFFFFFFFF" + "FA8000000000000000");
|
||||
assertEquals(0, r.readInt64());
|
||||
assertEquals(-1, r.readInt64());
|
||||
assertEquals(Long.MAX_VALUE, r.readInt64());
|
||||
assertEquals(Long.MIN_VALUE, r.readInt64());
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipInt64() throws Exception {
|
||||
setContents("FA0000000000000000");
|
||||
r.skipInt64();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadIntAny() throws Exception {
|
||||
setContents("00" + "7F" + "FD80" + "FDFF" + "FC0080" + "FC7FFF"
|
||||
+ "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000");
|
||||
assertEquals(0, r.readIntAny());
|
||||
assertEquals(127, r.readIntAny());
|
||||
assertEquals(-128, r.readIntAny());
|
||||
assertEquals(-1, r.readIntAny());
|
||||
assertEquals(128, r.readIntAny());
|
||||
assertEquals(32767, r.readIntAny());
|
||||
assertEquals(32768, r.readIntAny());
|
||||
assertEquals(2147483647, r.readIntAny());
|
||||
assertEquals(2147483648L, r.readIntAny());
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipIntAny() throws Exception {
|
||||
setContents("00" + "FD00" + "FC0000" + "FB00000000"
|
||||
+ "FA0000000000000000");
|
||||
r.skipIntAny();
|
||||
r.skipIntAny();
|
||||
r.skipIntAny();
|
||||
r.skipIntAny();
|
||||
r.skipIntAny();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFloat32() throws Exception {
|
||||
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
|
||||
// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
|
||||
setContents("F900000000" + "F93F800000" + "F940000000" + "F9BF800000"
|
||||
+ "F980000000" + "F9FF800000" + "F97F800000" + "F97FC00000");
|
||||
assertEquals(0F, r.readFloat32());
|
||||
assertEquals(1F, r.readFloat32());
|
||||
assertEquals(2F, r.readFloat32());
|
||||
assertEquals(-1F, r.readFloat32());
|
||||
assertEquals(-0F, r.readFloat32());
|
||||
assertEquals(Float.NEGATIVE_INFINITY, r.readFloat32());
|
||||
assertEquals(Float.POSITIVE_INFINITY, r.readFloat32());
|
||||
assertTrue(Float.isNaN(r.readFloat32()));
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipFloat32() throws Exception {
|
||||
setContents("F900000000");
|
||||
r.skipFloat32();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFloat64() throws Exception {
|
||||
setContents("F80000000000000000" + "F83FF0000000000000"
|
||||
+ "F84000000000000000" + "F8BFF0000000000000"
|
||||
+ "F88000000000000000" + "F8FFF0000000000000"
|
||||
+ "F87FF0000000000000" + "F87FF8000000000000");
|
||||
assertEquals(0.0, r.readFloat64());
|
||||
assertEquals(1.0, r.readFloat64());
|
||||
assertEquals(2.0, r.readFloat64());
|
||||
assertEquals(-1.0, r.readFloat64());
|
||||
assertEquals(-0.0, r.readFloat64());
|
||||
assertEquals(Double.NEGATIVE_INFINITY, r.readFloat64());
|
||||
assertEquals(Double.POSITIVE_INFINITY, r.readFloat64());
|
||||
assertTrue(Double.isNaN(r.readFloat64()));
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipFloat64() throws Exception {
|
||||
setContents("F80000000000000000");
|
||||
r.skipFloat64();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadString() throws Exception {
|
||||
setContents("F703666F6F" + "F700");
|
||||
assertEquals("foo", r.readString(Integer.MAX_VALUE));
|
||||
assertEquals("", r.readString(Integer.MAX_VALUE));
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStringMaxLength() throws Exception {
|
||||
setContents("F703666F6F" + "F703666F6F");
|
||||
assertEquals("foo", r.readString(3));
|
||||
try {
|
||||
r.readString(2);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipString() throws Exception {
|
||||
setContents("F703666F6F" + "F700");
|
||||
r.skipString(Integer.MAX_VALUE);
|
||||
r.skipString(Integer.MAX_VALUE);
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipStringMaxLength() throws Exception {
|
||||
setContents("F703666F6F" + "F703666F6F");
|
||||
r.skipString(3);
|
||||
try {
|
||||
r.skipString(2);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBytes() throws Exception {
|
||||
setContents("F603010203" + "F600");
|
||||
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(Integer.MAX_VALUE));
|
||||
assertArrayEquals(new byte[] {}, r.readBytes(Integer.MAX_VALUE));
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBytesMaxLength() throws Exception {
|
||||
setContents("F603010203" + "F603010203");
|
||||
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3));
|
||||
try {
|
||||
r.readBytes(2);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipBytes() throws Exception {
|
||||
setContents("F603010203" + "F600");
|
||||
r.skipBytes(Integer.MAX_VALUE);
|
||||
r.skipBytes(Integer.MAX_VALUE);
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipBytesMaxLength() throws Exception {
|
||||
setContents("F603010203" + "F603010203");
|
||||
r.skipBytes(3);
|
||||
try {
|
||||
r.skipBytes(2);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadList() throws Exception {
|
||||
setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
|
||||
r.readListStart();
|
||||
assertFalse(r.hasListEnd());
|
||||
assertEquals((byte) 1, r.readIntAny());
|
||||
assertFalse(r.hasListEnd());
|
||||
assertEquals("foo", r.readString(1000));
|
||||
assertFalse(r.hasListEnd());
|
||||
assertEquals((short) 128, r.readIntAny());
|
||||
assertTrue(r.hasListEnd());
|
||||
r.readListEnd();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipList() throws Exception {
|
||||
setContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
|
||||
r.skipList();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadMap() throws Exception {
|
||||
setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
|
||||
r.readMapStart();
|
||||
assertFalse(r.hasMapEnd());
|
||||
assertEquals("foo", r.readString(1000));
|
||||
assertFalse(r.hasMapEnd());
|
||||
assertEquals((byte) 123, r.readIntAny());
|
||||
assertFalse(r.hasMapEnd());
|
||||
assertArrayEquals(new byte[] {}, r.readBytes(1000));
|
||||
assertFalse(r.hasMapEnd());
|
||||
assertTrue(r.hasNull());
|
||||
r.readNull();
|
||||
assertTrue(r.hasMapEnd());
|
||||
r.readMapEnd();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipMap() throws Exception {
|
||||
setContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
|
||||
r.skipMap();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadStruct() throws Exception {
|
||||
// Two empty structs with IDs 0 and 255
|
||||
setContents("F300" + "F2" + "F3FF" + "F2");
|
||||
r.readStructStart(0);
|
||||
r.readStructEnd();
|
||||
r.readStructStart(255);
|
||||
r.readStructEnd();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipStruct() throws Exception {
|
||||
// Two empty structs with IDs 0 and 255
|
||||
setContents("F300" + "F2" + "F3FF" + "F2");
|
||||
r.skipStruct();
|
||||
r.skipStruct();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipNestedStructMapAndList() throws Exception {
|
||||
// A struct containing a map containing two empty lists
|
||||
setContents("F300" + "F4" + "F5" + "F2" + "F5" + "F2" + "F2" + "F2");
|
||||
r.skipStruct();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadNull() throws Exception {
|
||||
setContents("F1");
|
||||
r.readNull();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipNull() throws Exception {
|
||||
setContents("F1");
|
||||
r.skipNull();
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadEmptyInput() throws Exception {
|
||||
setContents("");
|
||||
assertTrue(r.eof());
|
||||
}
|
||||
|
||||
private void setContents(String hex) {
|
||||
in = new ByteArrayInputStream(StringUtils.fromHexString(hex));
|
||||
r = new ReaderImpl(in);
|
||||
}
|
||||
}
|
||||
244
briar-tests/src/org/briarproject/serial/WriterImplTest.java
Normal file
244
briar-tests/src/org/briarproject/serial/WriterImplTest.java
Normal file
@@ -0,0 +1,244 @@
|
||||
package org.briarproject.serial;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class WriterImplTest extends BriarTestCase {
|
||||
|
||||
private ByteArrayOutputStream out = null;
|
||||
private WriterImpl w = null;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
out = new ByteArrayOutputStream();
|
||||
w = new WriterImpl(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBoolean() throws IOException {
|
||||
w.writeBoolean(true);
|
||||
w.writeBoolean(false);
|
||||
// TRUE tag, FALSE tag
|
||||
checkContents("FE" + "FF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteUint7() throws IOException {
|
||||
w.writeUint7((byte) 0);
|
||||
w.writeUint7(Byte.MAX_VALUE);
|
||||
// 0, 127
|
||||
checkContents("00" + "7F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteInt8() throws IOException {
|
||||
w.writeInt8((byte) 0);
|
||||
w.writeInt8((byte) -1);
|
||||
w.writeInt8(Byte.MIN_VALUE);
|
||||
w.writeInt8(Byte.MAX_VALUE);
|
||||
// INT8 tag, 0, INT8 tag, -1, INT8 tag, -128, INT8 tag, 127
|
||||
checkContents("FD" + "00" + "FD" + "FF" + "FD" + "80" + "FD" + "7F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteInt16() throws IOException {
|
||||
w.writeInt16((short) 0);
|
||||
w.writeInt16((short) -1);
|
||||
w.writeInt16(Short.MIN_VALUE);
|
||||
w.writeInt16(Short.MAX_VALUE);
|
||||
// INT16 tag, 0, INT16 tag, -1, INT16 tag, -32768, INT16 tag, 32767
|
||||
checkContents("FC" + "0000" + "FC" + "FFFF" + "FC" + "8000"
|
||||
+ "FC" + "7FFF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteInt32() throws IOException {
|
||||
w.writeInt32(0);
|
||||
w.writeInt32(-1);
|
||||
w.writeInt32(Integer.MIN_VALUE);
|
||||
w.writeInt32(Integer.MAX_VALUE);
|
||||
// INT32 tag, 0, INT32 tag, -1, etc
|
||||
checkContents("FB" + "00000000" + "FB" + "FFFFFFFF" + "FB" + "80000000"
|
||||
+ "FB" + "7FFFFFFF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteInt64() throws IOException {
|
||||
w.writeInt64(0);
|
||||
w.writeInt64(-1);
|
||||
w.writeInt64(Long.MIN_VALUE);
|
||||
w.writeInt64(Long.MAX_VALUE);
|
||||
// INT64 tag, 0, INT64 tag, -1, etc
|
||||
checkContents("FA" + "0000000000000000" + "FA" + "FFFFFFFFFFFFFFFF"
|
||||
+ "FA" + "8000000000000000" + "FA" + "7FFFFFFFFFFFFFFF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteIntAny() throws IOException {
|
||||
w.writeIntAny(0); // uint7
|
||||
w.writeIntAny(-1); // int8
|
||||
w.writeIntAny(Byte.MAX_VALUE); // uint7
|
||||
w.writeIntAny(Byte.MAX_VALUE + 1); // int16
|
||||
w.writeIntAny(Short.MAX_VALUE); // int16
|
||||
w.writeIntAny(Short.MAX_VALUE + 1); // int32
|
||||
w.writeIntAny(Integer.MAX_VALUE); // int32
|
||||
w.writeIntAny(Integer.MAX_VALUE + 1L); // int64
|
||||
checkContents("00" + "FDFF" + "7F" + "FC0080" + "FC7FFF"
|
||||
+ "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteFloat32() throws IOException {
|
||||
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
|
||||
// 1 bit for sign, 8 for exponent, 23 for significand
|
||||
w.writeFloat32(0F); // 0 0 0 -> 0x00000000
|
||||
w.writeFloat32(1F); // 0 127 1 -> 0x3F800000
|
||||
w.writeFloat32(2F); // 0 128 1 -> 0x40000000
|
||||
w.writeFloat32(-1F); // 1 127 1 -> 0xBF800000
|
||||
w.writeFloat32(-0F); // 1 0 0 -> 0x80000000
|
||||
// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
|
||||
w.writeFloat32(Float.NEGATIVE_INFINITY); // 1 255 0 -> 0xFF800000
|
||||
w.writeFloat32(Float.POSITIVE_INFINITY); // 0 255 0 -> 0x7F800000
|
||||
w.writeFloat32(Float.NaN); // 0 255 1 -> 0x7FC00000
|
||||
checkContents("F9" + "00000000" + "F9" + "3F800000" + "F9" + "40000000"
|
||||
+ "F9" + "BF800000" + "F9" + "80000000" + "F9" + "FF800000"
|
||||
+ "F9" + "7F800000" + "F9" + "7FC00000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteFloat64() throws IOException {
|
||||
// 1 bit for sign, 11 for exponent, 52 for significand
|
||||
w.writeFloat64(0.0); // 0 0 0 -> 0x0000000000000000
|
||||
w.writeFloat64(1.0); // 0 1023 1 -> 0x3FF0000000000000
|
||||
w.writeFloat64(2.0); // 0 1024 1 -> 0x4000000000000000
|
||||
w.writeFloat64(-1.0); // 1 1023 1 -> 0xBFF0000000000000
|
||||
w.writeFloat64(-0.0); // 1 0 0 -> 0x8000000000000000
|
||||
w.writeFloat64(Double.NEGATIVE_INFINITY); // 1 2047 0 -> 0xFFF00000...
|
||||
w.writeFloat64(Double.POSITIVE_INFINITY); // 0 2047 0 -> 0x7FF00000...
|
||||
w.writeFloat64(Double.NaN); // 0 2047 1 -> 0x7FF8000000000000
|
||||
checkContents("F8" + "0000000000000000" + "F8" + "3FF0000000000000"
|
||||
+ "F8" + "4000000000000000" + "F8" + "BFF0000000000000"
|
||||
+ "F8" + "8000000000000000" + "F8" + "FFF0000000000000"
|
||||
+ "F8" + "7FF0000000000000" + "F8" + "7FF8000000000000");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteString() throws IOException {
|
||||
w.writeString("foo bar baz bam ");
|
||||
// STRING tag, length 16 as uint7, UTF-8 bytes
|
||||
checkContents("F7" + "10" + "666F6F206261722062617A2062616D20");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBytes() throws IOException {
|
||||
w.writeBytes(new byte[] {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
});
|
||||
// BYTES tag, length 16 as uint7, bytes
|
||||
checkContents("F6" + "10" + "000102030405060708090A0B0C0D0E0F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteList() throws IOException {
|
||||
List<Object> l = new ArrayList<Object>();
|
||||
for(int i = 0; i < 16; i++) l.add(i);
|
||||
w.writeList(l);
|
||||
// LIST tag, elements as uint7, END tag
|
||||
checkContents("F5" + "000102030405060708090A0B0C0D0E0F" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListCanContainNull() throws IOException {
|
||||
List<Object> l = new ArrayList<Object>();
|
||||
l.add(1);
|
||||
l.add(null);
|
||||
l.add(2);
|
||||
w.writeList(l);
|
||||
// LIST tag, 1 as uint7, null, 2 as uint7, END tag
|
||||
checkContents("F5" + "01" + "F1" + "02" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteMap() throws IOException {
|
||||
// Use LinkedHashMap to get predictable iteration order
|
||||
Map<Object, Object> m = new LinkedHashMap<Object, Object>();
|
||||
for(int i = 0; i < 16; i++) m.put(i, i + 1);
|
||||
w.writeMap(m);
|
||||
// MAP tag, entries as uint7, END tag
|
||||
checkContents("F4" + "0001" + "0102" + "0203" + "0304" + "0405"
|
||||
+ "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C"
|
||||
+ "0C0D" + "0D0E" + "0E0F" + "0F10" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteDelimitedList() throws IOException {
|
||||
w.writeListStart();
|
||||
w.writeIntAny((byte) 1); // Written as uint7
|
||||
w.writeString("foo"); // Written as string
|
||||
w.writeIntAny(128L); // Written as int16
|
||||
w.writeListEnd();
|
||||
// LIST tag, 1 as uint7, "foo" as string, 128 as int16, END tag
|
||||
checkContents("F5" + "01" + "F703666F6F" + "FC0080" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteDelimitedMap() throws IOException {
|
||||
w.writeMapStart();
|
||||
w.writeString("foo"); // Written as string
|
||||
w.writeIntAny(123); // Written as uint7
|
||||
w.writeBytes(new byte[0]); // Written as bytes
|
||||
w.writeNull();
|
||||
w.writeMapEnd();
|
||||
// MAP tag, "foo" as string, 123 as uint7, byte[0] as bytes,
|
||||
// NULL tag, END tag
|
||||
checkContents("F4" + "F703666F6F" + "7B" + "F600" + "F1" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteNestedMapsAndLists() throws IOException {
|
||||
Map<Object, Object> m = new LinkedHashMap<Object, Object>();
|
||||
m.put("foo", Integer.valueOf(123));
|
||||
List<Object> l = new ArrayList<Object>();
|
||||
l.add(Byte.valueOf((byte) 1));
|
||||
Map<Object, Object> m1 = new LinkedHashMap<Object, Object>();
|
||||
m1.put(m, l);
|
||||
w.writeMap(m1);
|
||||
// MAP tag, MAP tag, "foo" as string, 123 as uint7, END tag,
|
||||
// LIST tag, 1 as uint7, END tag, END tag
|
||||
checkContents("F4" + "F4" + "F703666F6F" + "7B" + "F2"
|
||||
+ "F5" + "01" + "F2" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteStruct() throws IOException {
|
||||
w.writeStructStart(123);
|
||||
w.writeStructEnd();
|
||||
// STRUCT tag, 123 as struct ID, END tag
|
||||
checkContents("F3" + "7B" + "F2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteNull() throws IOException {
|
||||
w.writeNull();
|
||||
checkContents("F1");
|
||||
}
|
||||
|
||||
private void checkContents(String hex) throws IOException {
|
||||
out.flush();
|
||||
out.close();
|
||||
byte[] expected = StringUtils.fromHexString(hex);
|
||||
assertTrue(StringUtils.toHexString(out.toByteArray()),
|
||||
Arrays.equals(expected, out.toByteArray()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConnectionReaderImplTest extends BriarTestCase {
|
||||
|
||||
private static final int FRAME_LENGTH = 1024;
|
||||
private static final int MAX_PAYLOAD_LENGTH =
|
||||
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
|
||||
@Test
|
||||
public void testEmptyFramesAreSkipped() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameReader reader = context.mock(FrameReader.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(0)); // Empty frame
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(2)); // Non-empty frame with two payload bytes
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(0)); // Empty frame
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(-1)); // No more frames
|
||||
}});
|
||||
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
|
||||
assertEquals(0, c.read()); // Skip the first empty frame, read a byte
|
||||
assertEquals(0, c.read()); // Read another byte
|
||||
assertEquals(-1, c.read()); // Skip the second empty frame, reach EOF
|
||||
assertEquals(-1, c.read()); // Still at EOF
|
||||
context.assertIsSatisfied();
|
||||
c.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameReader reader = context.mock(FrameReader.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(0)); // Empty frame
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(2)); // Non-empty frame with two payload bytes
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(0)); // Empty frame
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(-1)); // No more frames
|
||||
}});
|
||||
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
|
||||
byte[] buf = new byte[MAX_PAYLOAD_LENGTH];
|
||||
// Skip the first empty frame, read the two payload bytes
|
||||
assertEquals(2, c.read(buf));
|
||||
// Skip the second empty frame, reach EOF
|
||||
assertEquals(-1, c.read(buf));
|
||||
// Still at EOF
|
||||
assertEquals(-1, c.read(buf));
|
||||
context.assertIsSatisfied();
|
||||
c.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleReadsPerFrame() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameReader reader = context.mock(FrameReader.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(-1)); // No more frames
|
||||
}});
|
||||
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
|
||||
byte[] buf = new byte[MAX_PAYLOAD_LENGTH / 2];
|
||||
// Read the first half of the payload
|
||||
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf));
|
||||
// Read the second half of the payload
|
||||
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf));
|
||||
// Reach EOF
|
||||
assertEquals(-1, c.read(buf, 0, buf.length));
|
||||
context.assertIsSatisfied();
|
||||
c.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameReader reader = context.mock(FrameReader.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame
|
||||
oneOf(reader).readFrame(with(any(byte[].class)));
|
||||
will(returnValue(-1)); // No more frames
|
||||
}});
|
||||
ConnectionReaderImpl c = new ConnectionReaderImpl(reader, FRAME_LENGTH);
|
||||
byte[] buf = new byte[MAX_PAYLOAD_LENGTH];
|
||||
// Read the first half of the payload
|
||||
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf, MAX_PAYLOAD_LENGTH / 2,
|
||||
MAX_PAYLOAD_LENGTH / 2));
|
||||
// Read the second half of the payload
|
||||
assertEquals(MAX_PAYLOAD_LENGTH / 2, c.read(buf, 123,
|
||||
MAX_PAYLOAD_LENGTH / 2));
|
||||
// Reach EOF
|
||||
assertEquals(-1, c.read(buf, 0, buf.length));
|
||||
context.assertIsSatisfied();
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.transport.ConnectionRegistry;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConnectionRegistryImplTest extends BriarTestCase {
|
||||
|
||||
private final ContactId contactId, contactId1;
|
||||
private final TransportId transportId, transportId1;
|
||||
|
||||
public ConnectionRegistryImplTest() {
|
||||
contactId = new ContactId(1);
|
||||
contactId1 = new ContactId(2);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
transportId1 = new TransportId(TestUtils.getRandomId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterAndUnregister() {
|
||||
ConnectionRegistry c = new ConnectionRegistryImpl();
|
||||
// The registry should be empty
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId1));
|
||||
// Check that a registered connection shows up
|
||||
c.registerConnection(contactId, transportId);
|
||||
assertEquals(Arrays.asList(contactId),
|
||||
c.getConnectedContacts(transportId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId1));
|
||||
// Register an identical connection - lookup should be unaffected
|
||||
c.registerConnection(contactId, transportId);
|
||||
assertEquals(Arrays.asList(contactId),
|
||||
c.getConnectedContacts(transportId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId1));
|
||||
// Unregister one of the connections - lookup should be unaffected
|
||||
c.unregisterConnection(contactId, transportId);
|
||||
assertEquals(Arrays.asList(contactId),
|
||||
c.getConnectedContacts(transportId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId1));
|
||||
// Unregister the other connection - lookup should be affected
|
||||
c.unregisterConnection(contactId, transportId);
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
c.getConnectedContacts(transportId1));
|
||||
// Try to unregister the connection again - exception should be thrown
|
||||
try {
|
||||
c.unregisterConnection(contactId, transportId);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
// Register both contacts with one transport, one contact with both
|
||||
c.registerConnection(contactId, transportId);
|
||||
c.registerConnection(contactId1, transportId);
|
||||
c.registerConnection(contactId1, transportId1);
|
||||
Collection<ContactId> connected = c.getConnectedContacts(transportId);
|
||||
assertEquals(2, connected.size());
|
||||
assertTrue(connected.contains(contactId));
|
||||
assertTrue(connected.contains(contactId1));
|
||||
assertEquals(Arrays.asList(contactId1),
|
||||
c.getConnectedContacts(transportId1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE;
|
||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConnectionWindowTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testWindowSliding() {
|
||||
ConnectionWindow w = new ConnectionWindow();
|
||||
for(int i = 0; i < 100; i++) {
|
||||
assertFalse(w.isSeen(i));
|
||||
w.setSeen(i);
|
||||
assertTrue(w.isSeen(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowJumping() {
|
||||
ConnectionWindow w = new ConnectionWindow();
|
||||
for(int i = 0; i < 100; i += 13) {
|
||||
assertFalse(w.isSeen(i));
|
||||
w.setSeen(i);
|
||||
assertTrue(w.isSeen(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowUpperLimit() {
|
||||
ConnectionWindow w = new ConnectionWindow();
|
||||
// Centre is 0, highest value in window is 15
|
||||
w.setSeen(15);
|
||||
// Centre is 16, highest value in window is 31
|
||||
w.setSeen(31);
|
||||
try {
|
||||
// Centre is 32, highest value in window is 47
|
||||
w.setSeen(48);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
// Centre is max - 1, highest value in window is max
|
||||
byte[] bitmap = new byte[CONNECTION_WINDOW_SIZE / 8];
|
||||
w = new ConnectionWindow(MAX_32_BIT_UNSIGNED - 1, bitmap);
|
||||
assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED - 1));
|
||||
assertFalse(w.isSeen(MAX_32_BIT_UNSIGNED));
|
||||
// Values greater than max should never be allowed
|
||||
try {
|
||||
w.setSeen(MAX_32_BIT_UNSIGNED + 1);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
w.setSeen(MAX_32_BIT_UNSIGNED);
|
||||
assertTrue(w.isSeen(MAX_32_BIT_UNSIGNED));
|
||||
// Centre should have moved to max + 1
|
||||
assertEquals(MAX_32_BIT_UNSIGNED + 1, w.getCentre());
|
||||
// The bit corresponding to max should be set
|
||||
byte[] expectedBitmap = new byte[CONNECTION_WINDOW_SIZE / 8];
|
||||
expectedBitmap[expectedBitmap.length / 2 - 1] = 1; // 00000001
|
||||
assertArrayEquals(expectedBitmap, w.getBitmap());
|
||||
// Values greater than max should never be allowed even if centre > max
|
||||
try {
|
||||
w.setSeen(MAX_32_BIT_UNSIGNED + 1);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowLowerLimit() {
|
||||
ConnectionWindow w = new ConnectionWindow();
|
||||
// Centre is 0, negative values should never be allowed
|
||||
try {
|
||||
w.setSeen(-1);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
// Slide the window
|
||||
w.setSeen(15);
|
||||
// Centre is 16, lowest value in window is 0
|
||||
w.setSeen(0);
|
||||
// Slide the window
|
||||
w.setSeen(16);
|
||||
// Centre is 17, lowest value in window is 1
|
||||
w.setSeen(1);
|
||||
try {
|
||||
w.setSeen(0);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
// Slide the window
|
||||
w.setSeen(25);
|
||||
// Centre is 26, lowest value in window is 10
|
||||
w.setSeen(10);
|
||||
try {
|
||||
w.setSeen(9);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
// Centre should still be 26
|
||||
assertEquals(26, w.getCentre());
|
||||
// The bits corresponding to 10, 15, 16 and 25 should be set
|
||||
byte[] expectedBitmap = new byte[CONNECTION_WINDOW_SIZE / 8];
|
||||
expectedBitmap[0] = (byte) 134; // 10000110
|
||||
expectedBitmap[1] = 1; // 00000001
|
||||
assertArrayEquals(expectedBitmap, w.getBitmap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotSetSeenTwice() {
|
||||
ConnectionWindow w = new ConnectionWindow();
|
||||
w.setSeen(15);
|
||||
try {
|
||||
w.setSeen(15);
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnseenConnectionNumbers() {
|
||||
ConnectionWindow w = new ConnectionWindow();
|
||||
// Centre is 0; window should cover 0 to 15, inclusive, with none seen
|
||||
Collection<Long> unseen = w.getUnseen();
|
||||
assertEquals(16, unseen.size());
|
||||
for(int i = 0; i < 16; i++) {
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertFalse(w.isSeen(i));
|
||||
}
|
||||
w.setSeen(3);
|
||||
w.setSeen(4);
|
||||
// Centre is 5; window should cover 0 to 20, inclusive, with two seen
|
||||
unseen = w.getUnseen();
|
||||
assertEquals(19, unseen.size());
|
||||
for(int i = 0; i < 21; i++) {
|
||||
if(i == 3 || i == 4) {
|
||||
assertFalse(unseen.contains(Long.valueOf(i)));
|
||||
assertTrue(w.isSeen(i));
|
||||
} else {
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertFalse(w.isSeen(i));
|
||||
}
|
||||
}
|
||||
w.setSeen(19);
|
||||
// Centre is 20; window should cover 4 to 35, inclusive, with two seen
|
||||
unseen = w.getUnseen();
|
||||
assertEquals(30, unseen.size());
|
||||
for(int i = 4; i < 36; i++) {
|
||||
if(i == 4 || i == 19) {
|
||||
assertFalse(unseen.contains(Long.valueOf(i)));
|
||||
assertTrue(w.isSeen(i));
|
||||
} else {
|
||||
assertTrue(unseen.contains(Long.valueOf(i)));
|
||||
assertFalse(w.isSeen(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ConnectionWriterImplTest extends BriarTestCase {
|
||||
|
||||
private static final int FRAME_LENGTH = 1024;
|
||||
private static final int MAX_PAYLOAD_LENGTH =
|
||||
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
|
||||
@Test
|
||||
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameWriter writer = context.mock(FrameWriter.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Write an empty final frame
|
||||
oneOf(writer).writeFrame(with(any(byte[].class)), with(0),
|
||||
with(true));
|
||||
// Flush the stream
|
||||
oneOf(writer).flush();
|
||||
}});
|
||||
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
|
||||
c.close();
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushWithoutBufferedDataWritesFrame() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameWriter writer = context.mock(FrameWriter.class);
|
||||
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
|
||||
context.checking(new Expectations() {{
|
||||
// Flush the stream
|
||||
oneOf(writer).flush();
|
||||
}});
|
||||
c.flush();
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushWithBufferedDataWritesFrameAndFlushes()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameWriter writer = context.mock(FrameWriter.class);
|
||||
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
|
||||
context.checking(new Expectations() {{
|
||||
// Write a non-final frame with one payload byte
|
||||
oneOf(writer).writeFrame(with(any(byte[].class)), with(1),
|
||||
with(false));
|
||||
// Flush the stream
|
||||
oneOf(writer).flush();
|
||||
}});
|
||||
c.write(0);
|
||||
c.flush();
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleByteWritesWriteFullFrame() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameWriter writer = context.mock(FrameWriter.class);
|
||||
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
|
||||
context.checking(new Expectations() {{
|
||||
// Write a full non-final frame
|
||||
oneOf(writer).writeFrame(with(any(byte[].class)),
|
||||
with(MAX_PAYLOAD_LENGTH), with(false));
|
||||
}});
|
||||
for(int i = 0; i < MAX_PAYLOAD_LENGTH; i++) {
|
||||
c.write(0);
|
||||
}
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiByteWritesWriteFullFrames() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameWriter writer = context.mock(FrameWriter.class);
|
||||
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
|
||||
context.checking(new Expectations() {{
|
||||
// Write two full non-final frames
|
||||
exactly(2).of(writer).writeFrame(with(any(byte[].class)),
|
||||
with(MAX_PAYLOAD_LENGTH), with(false));
|
||||
}});
|
||||
// Sanity check
|
||||
assertEquals(0, MAX_PAYLOAD_LENGTH % 2);
|
||||
// Write two full payloads using four multi-byte writes
|
||||
byte[] b = new byte[MAX_PAYLOAD_LENGTH / 2];
|
||||
c.write(b);
|
||||
c.write(b);
|
||||
c.write(b);
|
||||
c.write(b);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final FrameWriter writer = context.mock(FrameWriter.class);
|
||||
ConnectionWriterImpl c = new ConnectionWriterImpl(writer, FRAME_LENGTH);
|
||||
context.checking(new Expectations() {{
|
||||
// Write two full non-final frames
|
||||
exactly(2).of(writer).writeFrame(with(any(byte[].class)),
|
||||
with(MAX_PAYLOAD_LENGTH), with(false));
|
||||
// Write a final frame with a one-byte payload
|
||||
oneOf(writer).writeFrame(with(any(byte[].class)), with(1),
|
||||
with(true));
|
||||
// Flush the stream
|
||||
oneOf(writer).flush();
|
||||
}});
|
||||
// Write two full payloads using one large multi-byte write
|
||||
byte[] b = new byte[MAX_PAYLOAD_LENGTH * 2 + 1];
|
||||
c.write(b);
|
||||
// There should be one byte left in the buffer
|
||||
c.close();
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
||||
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class IncomingEncryptionLayerTest extends BriarTestCase {
|
||||
|
||||
// FIXME: This is an integration test, not a unit test
|
||||
|
||||
private static final int FRAME_LENGTH = 1024;
|
||||
private static final int MAX_PAYLOAD_LENGTH =
|
||||
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final SecretKey frameKey;
|
||||
|
||||
public IncomingEncryptionLayerTest() {
|
||||
Injector i = Guice.createInjector(new CryptoModule(),
|
||||
new TestLifecycleModule());
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
frameCipher = crypto.getFrameCipher();
|
||||
frameKey = crypto.generateSecretKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadValidFrames() throws Exception {
|
||||
// Generate two valid frames
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, false);
|
||||
byte[] frame1 = generateFrame(1, FRAME_LENGTH, 123, false, false);
|
||||
// Concatenate the frames
|
||||
byte[] valid = new byte[FRAME_LENGTH * 2];
|
||||
System.arraycopy(frame, 0, valid, 0, FRAME_LENGTH);
|
||||
System.arraycopy(frame1, 0, valid, FRAME_LENGTH, FRAME_LENGTH);
|
||||
// Read the frames
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(valid);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
byte[] buf = new byte[FRAME_LENGTH - MAC_LENGTH];
|
||||
assertEquals(123, i.readFrame(buf));
|
||||
assertEquals(123, i.readFrame(buf));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncatedFrameThrowsException() throws Exception {
|
||||
// Generate a valid frame
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, false);
|
||||
// Chop off the last byte
|
||||
byte[] truncated = new byte[FRAME_LENGTH - 1];
|
||||
System.arraycopy(frame, 0, truncated, 0, FRAME_LENGTH - 1);
|
||||
// Try to read the frame, which should fail due to truncation
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(truncated);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
try {
|
||||
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedFrameThrowsException() throws Exception {
|
||||
// Generate a valid frame
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, false);
|
||||
// Modify a randomly chosen byte of the frame
|
||||
frame[(int) (Math.random() * FRAME_LENGTH)] ^= 1;
|
||||
// Try to read the frame, which should fail due to modification
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
try {
|
||||
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortNonFinalFrameThrowsException() throws Exception {
|
||||
// Generate a short non-final frame
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH - 1, 123, false, false);
|
||||
// Try to read the frame, which should fail due to invalid length
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
try {
|
||||
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortFinalFrameDoesNotThrowException() throws Exception {
|
||||
// Generate a short final frame
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH - 1, 123, true, false);
|
||||
// Read the frame
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
int length = i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
|
||||
assertEquals(123, length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidPayloadLengthThrowsException() throws Exception {
|
||||
// Generate a frame with an invalid payload length
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH, MAX_PAYLOAD_LENGTH + 1,
|
||||
false, false);
|
||||
// Try to read the frame, which should fail due to invalid length
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
try {
|
||||
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonZeroPaddingThrowsException() throws Exception {
|
||||
// Generate a frame with bad padding
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH, 123, false, true);
|
||||
// Try to read the frame, which should fail due to bad padding
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(frame);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
try {
|
||||
i.readFrame(new byte[FRAME_LENGTH - MAC_LENGTH]);
|
||||
fail();
|
||||
} catch(FormatException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotReadBeyondFinalFrame() throws Exception {
|
||||
// Generate a valid final frame and another valid final frame after it
|
||||
byte[] frame = generateFrame(0, FRAME_LENGTH, MAX_PAYLOAD_LENGTH, true,
|
||||
false);
|
||||
byte[] frame1 = generateFrame(1, FRAME_LENGTH, 123, true, false);
|
||||
// Concatenate the frames
|
||||
byte[] extraFrame = new byte[FRAME_LENGTH * 2];
|
||||
System.arraycopy(frame, 0, extraFrame, 0, FRAME_LENGTH);
|
||||
System.arraycopy(frame1, 0, extraFrame, FRAME_LENGTH, FRAME_LENGTH);
|
||||
// Read the final frame, which should first read the tag
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(extraFrame);
|
||||
IncomingEncryptionLayer i = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
byte[] buf = new byte[FRAME_LENGTH - MAC_LENGTH];
|
||||
assertEquals(MAX_PAYLOAD_LENGTH, i.readFrame(buf));
|
||||
// The frame after the final frame should not be read
|
||||
assertEquals(-1, i.readFrame(buf));
|
||||
}
|
||||
|
||||
private byte[] generateFrame(long frameNumber, int frameLength,
|
||||
int payloadLength, boolean finalFrame, boolean badPadding)
|
||||
throws Exception {
|
||||
byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH];
|
||||
byte[] plaintext = new byte[frameLength - MAC_LENGTH];
|
||||
byte[] ciphertext = new byte[frameLength];
|
||||
FrameEncoder.encodeIv(iv, frameNumber);
|
||||
FrameEncoder.encodeAad(aad, frameNumber, plaintext.length);
|
||||
frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad);
|
||||
FrameEncoder.encodeHeader(plaintext, finalFrame, payloadLength);
|
||||
if(badPadding) plaintext[HEADER_LENGTH + payloadLength] = 1;
|
||||
frameCipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
|
||||
return ciphertext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,598 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.system.Timer;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.ConnectionRecogniser;
|
||||
import org.briarproject.api.transport.Endpoint;
|
||||
import org.briarproject.api.transport.TemporarySecret;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
public class KeyManagerImplTest extends BriarTestCase {
|
||||
|
||||
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
|
||||
private static final long MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
||||
private static final long ROTATION_PERIOD_LENGTH =
|
||||
MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final byte[] secret0, secret1, secret2, secret3, secret4;
|
||||
private final byte[] initialSecret;
|
||||
|
||||
public KeyManagerImplTest() {
|
||||
contactId = new ContactId(234);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
secret0 = new byte[32];
|
||||
secret1 = new byte[32];
|
||||
secret2 = new byte[32];
|
||||
secret3 = new byte[32];
|
||||
secret4 = new byte[32];
|
||||
for(int i = 0; i < secret0.length; i++) secret0[i] = 1;
|
||||
for(int i = 0; i < secret1.length; i++) secret1[i] = 2;
|
||||
for(int i = 0; i < secret2.length; i++) secret2[i] = 3;
|
||||
for(int i = 0; i < secret3.length; i++) secret3[i] = 4;
|
||||
for(int i = 0; i < secret4.length; i++) secret4[i] = 5;
|
||||
initialSecret = new byte[32];
|
||||
for(int i = 0; i < initialSecret.length; i++) initialSecret[i] = 123;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAndStop() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.emptyMap()));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointAdded() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The secrets for periods 0 - 2 should be derived
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// endpointAdded() during rotation period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
||||
will(returnValue(secret0.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s0);
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointAddedAndGetConnectionContext() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The secrets for periods 0 - 2 should be derived
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// endpointAdded() during rotation period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
||||
will(returnValue(secret0.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s0);
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
// getConnectionContext()
|
||||
oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
|
||||
will(returnValue(0L));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
|
||||
ConnectionContext ctx =
|
||||
keyManager.getConnectionContext(contactId, transportId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret1, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(true, ctx.getAlice());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAtEpoch() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the epoch, the start of period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s0);
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAtStartOfPeriod2() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
// The secret for period 3 should be derived and stored
|
||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the start of period 2
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH));
|
||||
// The secret for period 3 should be derived and stored
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||
will(returnValue(secret3.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s3));
|
||||
// The secrets for periods 1 - 3 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
oneOf(connectionRecogniser).addSecret(s3);
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAtEndOfPeriod3() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
// The secrets for periods 3 and 4 should be derived and stored
|
||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
|
||||
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the end of period 3
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
|
||||
// The secrets for periods 3 and 4 should be derived from secret 1
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||
will(returnValue(secret3.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret3, 4);
|
||||
will(returnValue(secret4.clone()));
|
||||
// The new secrets should be stored
|
||||
oneOf(db).addSecrets(Arrays.asList(s3, s4));
|
||||
// The secrets for periods 2 - 4 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
oneOf(connectionRecogniser).addSecret(s3);
|
||||
oneOf(connectionRecogniser).addSecret(s4);
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAndRotateInSamePeriod() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the epoch, the start of period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s0);
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// run() during period 1: the secrets should not be affected
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + 1));
|
||||
// getConnectionContext()
|
||||
oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
|
||||
will(returnValue(0L));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.run();
|
||||
ConnectionContext ctx =
|
||||
keyManager.getConnectionContext(contactId, transportId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret1, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(true, ctx.getAlice());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAndRotateInNextPeriod() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
// The secret for period 3 should be derived and stored
|
||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the epoch, the start of period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s0);
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// run() during period 2: the secrets should be rotated
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH + 1));
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||
will(returnValue(secret3.clone()));
|
||||
oneOf(connectionRecogniser).removeSecret(contactId, transportId, 0);
|
||||
oneOf(db).addSecrets(Arrays.asList(s3));
|
||||
oneOf(connectionRecogniser).addSecret(s3);
|
||||
// getConnectionContext()
|
||||
oneOf(db).incrementConnectionCounter(contactId, transportId, 2);
|
||||
will(returnValue(0L));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.run();
|
||||
ConnectionContext ctx =
|
||||
keyManager.getConnectionContext(contactId, transportId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret2, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(true, ctx.getAlice());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAndRotateAWholePeriodLate() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
context.mock(ConnectionRecogniser.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
// The secrets for periods 3 and 4 should be derived and stored
|
||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
|
||||
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the epoch, the start of period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
// The secrets for periods 0 - 2 should be added to the recogniser
|
||||
oneOf(connectionRecogniser).addSecret(s0);
|
||||
oneOf(connectionRecogniser).addSecret(s1);
|
||||
oneOf(connectionRecogniser).addSecret(s2);
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// run() during period 3 (late): the secrets should be rotated
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + 2 * ROTATION_PERIOD_LENGTH + 1));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||
will(returnValue(secret3.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret3, 4);
|
||||
will(returnValue(secret4.clone()));
|
||||
oneOf(connectionRecogniser).removeSecret(contactId, transportId, 0);
|
||||
oneOf(connectionRecogniser).removeSecret(contactId, transportId, 1);
|
||||
oneOf(db).addSecrets(Arrays.asList(s3, s4));
|
||||
oneOf(connectionRecogniser).addSecret(s3);
|
||||
oneOf(connectionRecogniser).addSecret(s4);
|
||||
// getConnectionContext()
|
||||
oneOf(db).incrementConnectionCounter(contactId, transportId, 3);
|
||||
will(returnValue(0L));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
oneOf(connectionRecogniser).removeSecrets();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.run();
|
||||
ConnectionContext ctx =
|
||||
keyManager.getConnectionContext(contactId, transportId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret3, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(true, ctx.getAlice());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,894 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.api.system.Timer;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.ConnectionRecogniser;
|
||||
import org.briarproject.api.transport.Endpoint;
|
||||
import org.briarproject.api.transport.TemporarySecret;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.junit.Test;
|
||||
|
||||
public class KeyRotationIntegrationTest extends BriarTestCase {
|
||||
|
||||
private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
|
||||
private static final long MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
||||
private static final long ROTATION_PERIOD_LENGTH =
|
||||
MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
|
||||
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final byte[] secret0, secret1, secret2, secret3, secret4;
|
||||
private final byte[] key0, key1, key2, key3, key4;
|
||||
private final byte[] initialSecret;
|
||||
|
||||
public KeyRotationIntegrationTest() {
|
||||
contactId = new ContactId(234);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
secret0 = new byte[32];
|
||||
secret1 = new byte[32];
|
||||
secret2 = new byte[32];
|
||||
secret3 = new byte[32];
|
||||
secret4 = new byte[32];
|
||||
for(int i = 0; i < secret0.length; i++) secret0[i] = 1;
|
||||
for(int i = 0; i < secret1.length; i++) secret1[i] = 2;
|
||||
for(int i = 0; i < secret2.length; i++) secret2[i] = 3;
|
||||
for(int i = 0; i < secret3.length; i++) secret3[i] = 4;
|
||||
for(int i = 0; i < secret4.length; i++) secret4[i] = 5;
|
||||
key0 = new byte[32];
|
||||
key1 = new byte[32];
|
||||
key2 = new byte[32];
|
||||
key3 = new byte[32];
|
||||
key4 = new byte[32];
|
||||
for(int i = 0; i < key0.length; i++) key0[i] = 1;
|
||||
for(int i = 0; i < key1.length; i++) key1[i] = 2;
|
||||
for(int i = 0; i < key2.length; i++) key2[i] = 3;
|
||||
for(int i = 0; i < key3.length; i++) key3[i] = 4;
|
||||
for(int i = 0; i < key4.length; i++) key4[i] = 5;
|
||||
initialSecret = new byte[32];
|
||||
for(int i = 0; i < initialSecret.length; i++) initialSecret[i] = 123;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartAndStop() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.emptyMap()));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointAdded() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
final SecretKey k0 = context.mock(SecretKey.class, "k0");
|
||||
final SecretKey k1 = context.mock(SecretKey.class, "k1");
|
||||
final SecretKey k2 = context.mock(SecretKey.class, "k2");
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The secrets for periods 0 - 2 should be derived
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// endpointAdded() during rotation period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
||||
will(returnValue(secret0.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// stop()
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// Remove the listener and stop the timer
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointAddedAndGetConnectionContext() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
final SecretKey k0 = context.mock(SecretKey.class, "k0");
|
||||
final SecretKey k1 = context.mock(SecretKey.class, "k1");
|
||||
final SecretKey k2 = context.mock(SecretKey.class, "k2");
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The secrets for periods 0 - 2 should be derived
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// endpointAdded() during rotation period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
||||
will(returnValue(secret0.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// getConnectionContext()
|
||||
oneOf(db).incrementConnectionCounter(contactId, transportId, 1);
|
||||
will(returnValue(0L));
|
||||
// stop()
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// Remove the listener and stop the timer
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
|
||||
ConnectionContext ctx =
|
||||
keyManager.getConnectionContext(contactId, transportId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret1, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(true, ctx.getAlice());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointAddedAndAcceptConnection() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
final SecretKey k0 = context.mock(SecretKey.class, "k0");
|
||||
final SecretKey k1 = context.mock(SecretKey.class, "k1");
|
||||
final SecretKey k2 = context.mock(SecretKey.class, "k2");
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The secrets for periods 0 - 2 should be derived
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// endpointAdded() during rotation period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
oneOf(crypto).deriveNextSecret(initialSecret, 0);
|
||||
will(returnValue(secret0.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s0, s1, s2));
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// acceptConnection()
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)),
|
||||
with(k2), with(16L));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
oneOf(db).setConnectionWindow(contactId, transportId, 2, 1,
|
||||
new byte[] {0, 1, 0, 0});
|
||||
oneOf(k2).erase();
|
||||
// stop()
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the updated tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 1; i < 17; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// Remove the listener and stop the timer
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
|
||||
// Recognise the tag for connection 0 in period 2
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
encodeTag(tag, key2, 0);
|
||||
ConnectionContext ctx =
|
||||
connectionRecogniser.acceptConnection(transportId, tag);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret2, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(true, ctx.getAlice());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAtEpoch() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
final SecretKey k0 = context.mock(SecretKey.class, "k0");
|
||||
final SecretKey k1 = context.mock(SecretKey.class, "k1");
|
||||
final SecretKey k2 = context.mock(SecretKey.class, "k2");
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the epoch, the start of period 1
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH));
|
||||
// The recogniser should derive the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// Start the timer
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
// The recogniser should remove the tags for period 0
|
||||
oneOf(crypto).deriveTagKey(secret0, false);
|
||||
will(returnValue(k0));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k0),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k0).getEncoded();
|
||||
will(returnValue(key0));
|
||||
}
|
||||
oneOf(k0).erase();
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// Remove the listener and stop the timer
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAtStartOfPeriod2() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
final SecretKey k1 = context.mock(SecretKey.class, "k1");
|
||||
final SecretKey k2 = context.mock(SecretKey.class, "k2");
|
||||
final SecretKey k3 = context.mock(SecretKey.class, "k3");
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
// The secret for period 3 should be derived and stored
|
||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the start of period 2
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH));
|
||||
// The secret for period 3 should be derived and stored
|
||||
oneOf(crypto).deriveNextSecret(secret0, 1);
|
||||
will(returnValue(secret1.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||
will(returnValue(secret3.clone()));
|
||||
oneOf(db).addSecrets(Arrays.asList(s3));
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// The recogniser should derive the tags for period 3
|
||||
oneOf(crypto).deriveTagKey(secret3, false);
|
||||
will(returnValue(k3));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k3).getEncoded();
|
||||
will(returnValue(key3));
|
||||
}
|
||||
oneOf(k3).erase();
|
||||
// Start the timer
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
// The recogniser should derive the tags for period 1
|
||||
oneOf(crypto).deriveTagKey(secret1, false);
|
||||
will(returnValue(k1));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k1),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k1).getEncoded();
|
||||
will(returnValue(key1));
|
||||
}
|
||||
oneOf(k1).erase();
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// The recogniser should remove the tags for period 3
|
||||
oneOf(crypto).deriveTagKey(secret3, false);
|
||||
will(returnValue(k3));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k3).getEncoded();
|
||||
will(returnValue(key3));
|
||||
}
|
||||
oneOf(k3).erase();
|
||||
// Remove the listener and stop the timer
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSecretsAtEndOfPeriod3() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final Clock clock = context.mock(Clock.class);
|
||||
final Timer timer = context.mock(Timer.class);
|
||||
final SecretKey k2 = context.mock(SecretKey.class, "k2");
|
||||
final SecretKey k3 = context.mock(SecretKey.class, "k3");
|
||||
final SecretKey k4 = context.mock(SecretKey.class, "k4");
|
||||
|
||||
final ConnectionRecogniser connectionRecogniser =
|
||||
new ConnectionRecogniserImpl(crypto, db);
|
||||
final KeyManagerImpl keyManager = new KeyManagerImpl(crypto, db,
|
||||
connectionRecogniser, clock, timer);
|
||||
|
||||
// The DB contains the secrets for periods 0 - 2
|
||||
Endpoint ep = new Endpoint(contactId, transportId, EPOCH, true);
|
||||
final TemporarySecret s0 = new TemporarySecret(ep, 0, secret0.clone());
|
||||
final TemporarySecret s1 = new TemporarySecret(ep, 1, secret1.clone());
|
||||
final TemporarySecret s2 = new TemporarySecret(ep, 2, secret2.clone());
|
||||
// The secrets for periods 3 and 4 should be derived and stored
|
||||
final TemporarySecret s3 = new TemporarySecret(ep, 3, secret3.clone());
|
||||
final TemporarySecret s4 = new TemporarySecret(ep, 4, secret4.clone());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// start()
|
||||
oneOf(db).addListener(with(any(EventListener.class)));
|
||||
oneOf(db).getSecrets();
|
||||
will(returnValue(Arrays.asList(s0, s1, s2)));
|
||||
oneOf(db).getTransportLatencies();
|
||||
will(returnValue(Collections.singletonMap(transportId,
|
||||
MAX_LATENCY)));
|
||||
// The current time is the end of period 3
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
|
||||
// The secrets for periods 3 and 4 should be derived from secret 1
|
||||
oneOf(crypto).deriveNextSecret(secret1, 2);
|
||||
will(returnValue(secret2.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret2, 3);
|
||||
will(returnValue(secret3.clone()));
|
||||
oneOf(crypto).deriveNextSecret(secret3, 4);
|
||||
will(returnValue(secret4.clone()));
|
||||
// The new secrets should be stored
|
||||
oneOf(db).addSecrets(Arrays.asList(s3, s4));
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// The recogniser should derive the tags for period 3
|
||||
oneOf(crypto).deriveTagKey(secret3, false);
|
||||
will(returnValue(k3));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k3).getEncoded();
|
||||
will(returnValue(key3));
|
||||
}
|
||||
oneOf(k3).erase();
|
||||
// The recogniser should derive the tags for period 4
|
||||
oneOf(crypto).deriveTagKey(secret4, false);
|
||||
will(returnValue(k4));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k4),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k4).getEncoded();
|
||||
will(returnValue(key4));
|
||||
}
|
||||
oneOf(k4).erase();
|
||||
// Start the timer
|
||||
oneOf(timer).scheduleAtFixedRate(with(keyManager),
|
||||
with(any(long.class)), with(any(long.class)));
|
||||
// stop()
|
||||
// The recogniser should derive the tags for period 2
|
||||
oneOf(crypto).deriveTagKey(secret2, false);
|
||||
will(returnValue(k2));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k2),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k2).getEncoded();
|
||||
will(returnValue(key2));
|
||||
}
|
||||
oneOf(k2).erase();
|
||||
// The recogniser should remove the tags for period 3
|
||||
oneOf(crypto).deriveTagKey(secret3, false);
|
||||
will(returnValue(k3));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k3),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k3).getEncoded();
|
||||
will(returnValue(key3));
|
||||
}
|
||||
oneOf(k3).erase();
|
||||
// The recogniser should derive the tags for period 4
|
||||
oneOf(crypto).deriveTagKey(secret4, false);
|
||||
will(returnValue(k4));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(k4),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
oneOf(k4).getEncoded();
|
||||
will(returnValue(key4));
|
||||
}
|
||||
oneOf(k4).erase();
|
||||
// Remove the listener and stop the timer
|
||||
oneOf(db).removeListener(with(any(EventListener.class)));
|
||||
oneOf(timer).cancel();
|
||||
}});
|
||||
|
||||
assertTrue(keyManager.start());
|
||||
keyManager.stop();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private void encodeTag(byte[] tag, byte[] rawKey, long connection) {
|
||||
// Encode a fake tag based on the key and connection number
|
||||
System.arraycopy(rawKey, 0, tag, 0, tag.length);
|
||||
ByteUtils.writeUint32(connection, tag, 0);
|
||||
}
|
||||
|
||||
private class EncodeTagAction implements Action {
|
||||
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Encodes a tag");
|
||||
}
|
||||
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
byte[] tag = (byte[]) invocation.getParameter(0);
|
||||
SecretKey key = (SecretKey) invocation.getParameter(1);
|
||||
long connection = (Long) invocation.getParameter(2);
|
||||
encodeTag(tag, key.getEncoded(), connection);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
||||
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
public class OutgoingEncryptionLayerTest extends BriarTestCase {
|
||||
|
||||
// FIXME: This is an integration test, not a unit test
|
||||
|
||||
private static final int FRAME_LENGTH = 1024;
|
||||
private static final int MAX_PAYLOAD_LENGTH =
|
||||
FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final byte[] tag;
|
||||
|
||||
public OutgoingEncryptionLayerTest() {
|
||||
Injector i = Guice.createInjector(new CryptoModule(),
|
||||
new TestLifecycleModule());
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
frameCipher = crypto.getFrameCipher();
|
||||
tag = new byte[TAG_LENGTH];
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryption() throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] iv = new byte[IV_LENGTH], aad = new byte[AAD_LENGTH];
|
||||
byte[] plaintext = new byte[FRAME_LENGTH - MAC_LENGTH];
|
||||
byte[] ciphertext = new byte[FRAME_LENGTH];
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
// Calculate the expected ciphertext
|
||||
FrameEncoder.encodeIv(iv, 0);
|
||||
FrameEncoder.encodeAad(aad, 0, plaintext.length);
|
||||
frameCipher.init(ENCRYPT_MODE, frameKey, iv, aad);
|
||||
FrameEncoder.encodeHeader(plaintext, false, payloadLength);
|
||||
frameCipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
|
||||
// Check that the actual tag and ciphertext match what's expected
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
|
||||
10 * FRAME_LENGTH, frameCipher, frameKey, FRAME_LENGTH, tag);
|
||||
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], payloadLength, false);
|
||||
byte[] actual = out.toByteArray();
|
||||
assertEquals(TAG_LENGTH + FRAME_LENGTH, actual.length);
|
||||
for(int i = 0; i < TAG_LENGTH; i++) assertEquals(tag[i], actual[i]);
|
||||
for(int i = 0; i < FRAME_LENGTH; i++) {
|
||||
assertEquals("" + i, ciphertext[i], actual[TAG_LENGTH + i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitiatorClosesConnectionWithoutWriting() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// Initiator's constructor
|
||||
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
|
||||
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
|
||||
FRAME_LENGTH, tag);
|
||||
// Write an empty final frame without having written any other frames
|
||||
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true);
|
||||
// Nothing should be written to the output stream
|
||||
assertEquals(0, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponderClosesConnectionWithoutWriting() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// Responder's constructor
|
||||
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
|
||||
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
|
||||
FRAME_LENGTH);
|
||||
// Write an empty final frame without having written any other frames
|
||||
o.writeFrame(new byte[FRAME_LENGTH - MAC_LENGTH], 0, true);
|
||||
// An empty final frame should be written to the output stream
|
||||
assertEquals(HEADER_LENGTH + MAC_LENGTH, out.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemainingCapacityWithTag() throws Exception {
|
||||
int MAX_PAYLOAD_LENGTH = FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// Initiator's constructor
|
||||
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
|
||||
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
|
||||
FRAME_LENGTH, tag);
|
||||
// There should be space for nine full frames and one partial frame
|
||||
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
|
||||
assertEquals(10 * MAX_PAYLOAD_LENGTH - TAG_LENGTH,
|
||||
o.getRemainingCapacity());
|
||||
// Write nine frames, each containing a partial payload
|
||||
for(int i = 0; i < 9; i++) {
|
||||
o.writeFrame(frame, 123, false);
|
||||
assertEquals((9 - i) * MAX_PAYLOAD_LENGTH - TAG_LENGTH,
|
||||
o.getRemainingCapacity());
|
||||
}
|
||||
// Write the final frame, which will not be padded
|
||||
o.writeFrame(frame, 123, true);
|
||||
int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH;
|
||||
assertEquals(MAX_PAYLOAD_LENGTH - TAG_LENGTH - finalFrameLength,
|
||||
o.getRemainingCapacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemainingCapacityWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// Responder's constructor
|
||||
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
|
||||
10 * FRAME_LENGTH, frameCipher, crypto.generateSecretKey(),
|
||||
FRAME_LENGTH);
|
||||
// There should be space for ten full frames
|
||||
assertEquals(10 * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());
|
||||
// Write nine frames, each containing a partial payload
|
||||
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
|
||||
for(int i = 0; i < 9; i++) {
|
||||
o.writeFrame(frame, 123, false);
|
||||
assertEquals((9 - i) * MAX_PAYLOAD_LENGTH,
|
||||
o.getRemainingCapacity());
|
||||
}
|
||||
// Write the final frame, which will not be padded
|
||||
o.writeFrame(frame, 123, true);
|
||||
int finalFrameLength = HEADER_LENGTH + 123 + MAC_LENGTH;
|
||||
assertEquals(MAX_PAYLOAD_LENGTH - finalFrameLength,
|
||||
o.getRemainingCapacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemainingCapacityLimitedByFrameNumbers() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// The connection has plenty of space so we're limited by frame numbers
|
||||
OutgoingEncryptionLayer o = new OutgoingEncryptionLayer(out,
|
||||
Long.MAX_VALUE, frameCipher, crypto.generateSecretKey(),
|
||||
FRAME_LENGTH);
|
||||
// There should be enough frame numbers for 2^32 frames
|
||||
assertEquals((1L << 32) * MAX_PAYLOAD_LENGTH, o.getRemainingCapacity());
|
||||
// Write a frame containing a partial payload
|
||||
byte[] frame = new byte[FRAME_LENGTH - MAC_LENGTH];
|
||||
o.writeFrame(frame, 123, false);
|
||||
// There should be enough frame numbers for 2^32 - 1 frames
|
||||
assertEquals(((1L << 32) - 1) * MAX_PAYLOAD_LENGTH,
|
||||
o.getRemainingCapacity());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.TemporarySecret;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TransportConnectionRecogniserTest extends BriarTestCase {
|
||||
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
private final TransportId transportId =
|
||||
new TransportId(TestUtils.getRandomId());
|
||||
|
||||
@Test
|
||||
public void testAddAndRemoveSecret() {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final byte[] secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
final boolean alice = false;
|
||||
final SecretKey tagKey = context.mock(SecretKey.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Add secret
|
||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
||||
will(returnValue(tagKey));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
oneOf(tagKey).erase();
|
||||
// Remove secret
|
||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
||||
will(returnValue(tagKey));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
oneOf(tagKey).erase();
|
||||
}});
|
||||
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
|
||||
alice, 0, secret, 0, 0, new byte[4]);
|
||||
TransportConnectionRecogniser recogniser =
|
||||
new TransportConnectionRecogniser(crypto, db, transportId);
|
||||
recogniser.addSecret(s);
|
||||
recogniser.removeSecret(contactId, 0);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptConnection() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
final byte[] secret = new byte[32];
|
||||
new Random().nextBytes(secret);
|
||||
final boolean alice = false;
|
||||
final SecretKey tagKey = context.mock(SecretKey.class);
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Add secret
|
||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
||||
will(returnValue(tagKey));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
||||
with((long) i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
oneOf(tagKey).erase();
|
||||
// Accept connection 0
|
||||
oneOf(crypto).deriveTagKey(secret, !alice);
|
||||
will(returnValue(tagKey));
|
||||
// The window should slide to include connection 16
|
||||
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagKey),
|
||||
with(16L));
|
||||
will(new EncodeTagAction());
|
||||
// The updated window should be stored
|
||||
oneOf(db).setConnectionWindow(contactId, transportId, 0, 1,
|
||||
new byte[] {0, 1, 0, 0});
|
||||
oneOf(tagKey).erase();
|
||||
// Accept connection again - no expectations
|
||||
}});
|
||||
TemporarySecret s = new TemporarySecret(contactId, transportId, 123,
|
||||
alice, 0, secret, 0, 0, new byte[4]);
|
||||
TransportConnectionRecogniser recogniser =
|
||||
new TransportConnectionRecogniser(crypto, db, transportId);
|
||||
recogniser.addSecret(s);
|
||||
// Connection 0 should be expected
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
ConnectionContext ctx = recogniser.acceptConnection(tag);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertArrayEquals(secret, ctx.getSecret());
|
||||
assertEquals(0, ctx.getConnectionNumber());
|
||||
assertEquals(alice, ctx.getAlice());
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private static class EncodeTagAction implements Action {
|
||||
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Encodes a tag");
|
||||
}
|
||||
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
byte[] tag = (byte[]) invocation.getParameter(0);
|
||||
long connection = (Long) invocation.getParameter(2);
|
||||
// Encode a fake tag based on the connection number
|
||||
ByteUtils.writeUint32(connection, tag, 0);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package org.briarproject.transport;
|
||||
|
||||
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Random;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.api.ContactId;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.transport.ConnectionContext;
|
||||
import org.briarproject.api.transport.ConnectionWriter;
|
||||
import org.briarproject.api.transport.ConnectionWriterFactory;
|
||||
import org.briarproject.crypto.CryptoModule;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
|
||||
public class TransportIntegrationTest extends BriarTestCase {
|
||||
|
||||
private final int FRAME_LENGTH = 2048;
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final ConnectionWriterFactory connectionWriterFactory;
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final Random random;
|
||||
private final byte[] secret;
|
||||
private final SecretKey frameKey;
|
||||
|
||||
public TransportIntegrationTest() {
|
||||
Module testModule = new AbstractModule() {
|
||||
public void configure() {
|
||||
bind(ConnectionWriterFactory.class).to(
|
||||
ConnectionWriterFactoryImpl.class);
|
||||
}
|
||||
};
|
||||
Injector i = Guice.createInjector(testModule, new CryptoModule(),
|
||||
new TestLifecycleModule());
|
||||
crypto = i.getInstance(CryptoComponent.class);
|
||||
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
|
||||
contactId = new ContactId(234);
|
||||
transportId = new TransportId(TestUtils.getRandomId());
|
||||
frameCipher = crypto.getFrameCipher();
|
||||
random = new Random();
|
||||
// Since we're sending frames to ourselves, we only need outgoing keys
|
||||
secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
frameKey = crypto.deriveFrameKey(secret, 0, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitiatorWriteAndRead() throws Exception {
|
||||
testWriteAndRead(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponderWriteAndRead() throws Exception {
|
||||
testWriteAndRead(false);
|
||||
}
|
||||
|
||||
private void testWriteAndRead(boolean initiator) throws Exception {
|
||||
// Generate two random frames
|
||||
byte[] frame = new byte[1234];
|
||||
random.nextBytes(frame);
|
||||
byte[] frame1 = new byte[321];
|
||||
random.nextBytes(frame1);
|
||||
// Copy the frame key - the copy will be erased
|
||||
SecretKey frameCopy = frameKey.copy();
|
||||
// Write the frames
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
FrameWriter encryptionOut = new OutgoingEncryptionLayer(out,
|
||||
Long.MAX_VALUE, frameCipher, frameCopy, FRAME_LENGTH);
|
||||
ConnectionWriterImpl writer = new ConnectionWriterImpl(encryptionOut,
|
||||
FRAME_LENGTH);
|
||||
OutputStream out1 = writer.getOutputStream();
|
||||
out1.write(frame);
|
||||
out1.flush();
|
||||
out1.write(frame1);
|
||||
out1.flush();
|
||||
byte[] output = out.toByteArray();
|
||||
assertEquals(FRAME_LENGTH * 2, output.length);
|
||||
// Read the tag and the frames back
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(output);
|
||||
FrameReader encryptionIn = new IncomingEncryptionLayer(in, frameCipher,
|
||||
frameKey, FRAME_LENGTH);
|
||||
ConnectionReaderImpl reader = new ConnectionReaderImpl(encryptionIn,
|
||||
FRAME_LENGTH);
|
||||
InputStream in1 = reader.getInputStream();
|
||||
byte[] recovered = new byte[frame.length];
|
||||
int offset = 0;
|
||||
while(offset < recovered.length) {
|
||||
int read = in1.read(recovered, offset, recovered.length - offset);
|
||||
if(read == -1) break;
|
||||
offset += read;
|
||||
}
|
||||
assertEquals(recovered.length, offset);
|
||||
assertArrayEquals(frame, recovered);
|
||||
byte[] recovered1 = new byte[frame1.length];
|
||||
offset = 0;
|
||||
while(offset < recovered1.length) {
|
||||
int read = in1.read(recovered1, offset, recovered1.length - offset);
|
||||
if(read == -1) break;
|
||||
offset += read;
|
||||
}
|
||||
assertEquals(recovered1.length, offset);
|
||||
assertArrayEquals(frame1, recovered1);
|
||||
writer.close();
|
||||
reader.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverheadWithTag() throws Exception {
|
||||
ByteArrayOutputStream out =
|
||||
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret, 0, true);
|
||||
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
|
||||
MAX_FRAME_LENGTH, MIN_CONNECTION_LENGTH, ctx, false, true);
|
||||
// Check that the connection writer thinks there's room for a packet
|
||||
long capacity = w.getRemainingCapacity();
|
||||
assertTrue(capacity > MAX_PACKET_LENGTH);
|
||||
assertTrue(capacity < MIN_CONNECTION_LENGTH);
|
||||
// Check that there really is room for a packet
|
||||
byte[] payload = new byte[MAX_PACKET_LENGTH];
|
||||
w.getOutputStream().write(payload);
|
||||
w.getOutputStream().close();
|
||||
long used = out.size();
|
||||
assertTrue(used > MAX_PACKET_LENGTH);
|
||||
assertTrue(used <= MIN_CONNECTION_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverheadWithoutTag() throws Exception {
|
||||
ByteArrayOutputStream out =
|
||||
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
|
||||
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
|
||||
secret, 0, true);
|
||||
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
|
||||
MAX_FRAME_LENGTH, MIN_CONNECTION_LENGTH, ctx, false, false);
|
||||
// Check that the connection writer thinks there's room for a packet
|
||||
long capacity = w.getRemainingCapacity();
|
||||
assertTrue(capacity > MAX_PACKET_LENGTH);
|
||||
assertTrue(capacity < MIN_CONNECTION_LENGTH);
|
||||
// Check that there really is room for a packet
|
||||
byte[] payload = new byte[MAX_PACKET_LENGTH];
|
||||
w.getOutputStream().write(payload);
|
||||
w.getOutputStream().close();
|
||||
long used = out.size();
|
||||
assertTrue(used > MAX_PACKET_LENGTH);
|
||||
assertTrue(used <= MIN_CONNECTION_LENGTH);
|
||||
}
|
||||
}
|
||||
66
briar-tests/src/org/briarproject/util/ByteUtilsTest.java
Normal file
66
briar-tests/src/org/briarproject/util/ByteUtilsTest.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package org.briarproject.util;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ByteUtilsTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testReadUint16() {
|
||||
byte[] b = StringUtils.fromHexString("000000");
|
||||
assertEquals(0, ByteUtils.readUint16(b, 1));
|
||||
b = StringUtils.fromHexString("000001");
|
||||
assertEquals(1, ByteUtils.readUint16(b, 1));
|
||||
b = StringUtils.fromHexString("00FFFF");
|
||||
assertEquals(65535, ByteUtils.readUint16(b, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadUint32() {
|
||||
byte[] b = StringUtils.fromHexString("0000000000");
|
||||
assertEquals(0, ByteUtils.readUint32(b, 1));
|
||||
b = StringUtils.fromHexString("0000000001");
|
||||
assertEquals(1, ByteUtils.readUint32(b, 1));
|
||||
b = StringUtils.fromHexString("00FFFFFFFF");
|
||||
assertEquals(4294967295L, ByteUtils.readUint32(b, 1));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWriteUint16() {
|
||||
byte[] b = new byte[3];
|
||||
ByteUtils.writeUint16(0, b, 1);
|
||||
assertEquals("000000", StringUtils.toHexString(b));
|
||||
ByteUtils.writeUint16(1, b, 1);
|
||||
assertEquals("000001", StringUtils.toHexString(b));
|
||||
ByteUtils.writeUint16(65535, b, 1);
|
||||
assertEquals("00FFFF", StringUtils.toHexString(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteUint32() {
|
||||
byte[] b = new byte[5];
|
||||
ByteUtils.writeUint32(0, b, 1);
|
||||
assertEquals("0000000000", StringUtils.toHexString(b));
|
||||
ByteUtils.writeUint32(1, b, 1);
|
||||
assertEquals("0000000001", StringUtils.toHexString(b));
|
||||
ByteUtils.writeUint32(4294967295L, b, 1);
|
||||
assertEquals("00FFFFFFFF", StringUtils.toHexString(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadUint() {
|
||||
byte[] b = new byte[1];
|
||||
b[0] = (byte) 128;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
assertEquals(1 << i, ByteUtils.readUint(b, i + 1));
|
||||
}
|
||||
b = new byte[2];
|
||||
for(int i = 0; i < 65535; i++) {
|
||||
ByteUtils.writeUint16(i, b, 0);
|
||||
assertEquals(i, ByteUtils.readUint(b, 16));
|
||||
assertEquals(i >> 1, ByteUtils.readUint(b, 15));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
briar-tests/src/org/briarproject/util/StringUtilsTest.java
Normal file
44
briar-tests/src/org/briarproject/util/StringUtilsTest.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package org.briarproject.util;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import org.briarproject.BriarTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class StringUtilsTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testHead() {
|
||||
String head = StringUtils.head("123456789", 5);
|
||||
assertEquals("12345...", head);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTail() {
|
||||
String tail = StringUtils.tail("987654321", 5);
|
||||
assertEquals("...54321", tail);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToHexString() {
|
||||
byte[] b = new byte[] {1, 2, 3, 127, -128};
|
||||
String s = StringUtils.toHexString(b);
|
||||
assertEquals("0102037F80", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromHexString() {
|
||||
try {
|
||||
StringUtils.fromHexString("12345");
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
try {
|
||||
StringUtils.fromHexString("ABCDEFGH");
|
||||
fail();
|
||||
} catch(IllegalArgumentException expected) {}
|
||||
byte[] b = StringUtils.fromHexString("0102037F80");
|
||||
assertArrayEquals(new byte[] {1, 2, 3, 127, -128}, b);
|
||||
b = StringUtils.fromHexString("0a0b0c0d0e0f");
|
||||
assertArrayEquals(new byte[] {10, 11, 12, 13, 14, 15}, b);
|
||||
}
|
||||
}
|
||||
202
briar-tests/src/org/briarproject/util/ZipUtilsTest.java
Normal file
202
briar-tests/src/org/briarproject/util/ZipUtilsTest.java
Normal file
@@ -0,0 +1,202 @@
|
||||
package org.briarproject.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.util.ZipUtils.Callback;
|
||||
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ZipUtilsTest extends BriarTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
|
||||
private final File f1 = new File(testDir, "abc/def/1");
|
||||
private final File f2 = new File(testDir, "abc/def/2");
|
||||
private final File f3 = new File(testDir, "abc/3");
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToZip() throws IOException {
|
||||
File src = new File(testDir, "src");
|
||||
File dest = new File(testDir, "dest");
|
||||
TestUtils.createFile(src, "foo bar baz");
|
||||
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dest));
|
||||
|
||||
ZipUtils.copyToZip("abc/def", src, zip);
|
||||
zip.flush();
|
||||
zip.close();
|
||||
|
||||
Map<String, String> expected = Collections.singletonMap("abc/def",
|
||||
"foo bar baz");
|
||||
checkZipEntries(dest, expected);
|
||||
}
|
||||
|
||||
private void checkZipEntries(File f, Map<String, String> expected)
|
||||
throws IOException {
|
||||
Map<String, String> found = new HashMap<String, String>();
|
||||
assertTrue(f.exists());
|
||||
assertTrue(f.isFile());
|
||||
ZipInputStream unzip = new ZipInputStream(new FileInputStream(f));
|
||||
ZipEntry entry;
|
||||
while((entry = unzip.getNextEntry()) != null) {
|
||||
String name = entry.getName();
|
||||
Scanner s = new Scanner(unzip);
|
||||
assertTrue(s.hasNextLine());
|
||||
String contents = s.nextLine();
|
||||
assertFalse(s.hasNextLine());
|
||||
unzip.closeEntry();
|
||||
found.put(name, contents);
|
||||
}
|
||||
unzip.close();
|
||||
assertEquals(expected.size(), found.size());
|
||||
for(String name : expected.keySet()) {
|
||||
String contents = found.get(name);
|
||||
assertNotNull(contents);
|
||||
assertEquals(expected.get(name), contents);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToZipRecursively() throws IOException {
|
||||
Mockery context = new Mockery();
|
||||
final Callback callback = context.mock(Callback.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(callback).processingFile(f1);
|
||||
oneOf(callback).processingFile(f2);
|
||||
oneOf(callback).processingFile(f3);
|
||||
}});
|
||||
|
||||
copyRecursively(callback);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyToZipRecursivelyNoCallback() throws IOException {
|
||||
copyRecursively(null);
|
||||
}
|
||||
|
||||
private void copyRecursively(Callback callback) throws IOException {
|
||||
TestUtils.createFile(f1, "one one one");
|
||||
TestUtils.createFile(f2, "two two two");
|
||||
TestUtils.createFile(f3, "three three three");
|
||||
File src = new File(testDir, "abc");
|
||||
File dest = new File(testDir, "dest");
|
||||
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dest));
|
||||
|
||||
ZipUtils.copyToZipRecursively("ghi", src, zip, callback);
|
||||
zip.flush();
|
||||
zip.close();
|
||||
|
||||
Map<String, String> expected = new HashMap<String, String>();
|
||||
expected.put("ghi/def/1", "one one one");
|
||||
expected.put("ghi/def/2", "two two two");
|
||||
expected.put("ghi/3", "three three three");
|
||||
checkZipEntries(dest, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnzipStream() throws IOException {
|
||||
Mockery context = new Mockery();
|
||||
final Callback callback = context.mock(Callback.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(callback).processingFile(f1);
|
||||
oneOf(callback).processingFile(f2);
|
||||
oneOf(callback).processingFile(f3);
|
||||
}});
|
||||
|
||||
unzipStream(null, callback);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertTrue(f1.exists());
|
||||
assertTrue(f1.isFile());
|
||||
assertEquals("one one one".length(), f1.length());
|
||||
assertTrue(f2.exists());
|
||||
assertTrue(f2.isFile());
|
||||
assertEquals("two two two".length(), f2.length());
|
||||
assertTrue(f3.exists());
|
||||
assertTrue(f3.isFile());
|
||||
assertEquals("three three three".length(), f3.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnzipStreamWithRegex() throws IOException {
|
||||
Mockery context = new Mockery();
|
||||
final Callback callback = context.mock(Callback.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(callback).processingFile(f1);
|
||||
oneOf(callback).processingFile(f2);
|
||||
}});
|
||||
|
||||
unzipStream("^abc/def/.*", callback);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertTrue(f1.exists());
|
||||
assertTrue(f1.isFile());
|
||||
assertEquals("one one one".length(), f1.length());
|
||||
assertTrue(f2.exists());
|
||||
assertTrue(f2.isFile());
|
||||
assertEquals("two two two".length(), f2.length());
|
||||
assertFalse(f3.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnzipStreamNoCallback() throws IOException {
|
||||
unzipStream(null, null);
|
||||
|
||||
assertTrue(f1.exists());
|
||||
assertTrue(f1.isFile());
|
||||
assertEquals("one one one".length(), f1.length());
|
||||
assertTrue(f2.exists());
|
||||
assertTrue(f2.isFile());
|
||||
assertEquals("two two two".length(), f2.length());
|
||||
assertTrue(f3.exists());
|
||||
assertTrue(f3.isFile());
|
||||
assertEquals("three three three".length(), f3.length());
|
||||
}
|
||||
|
||||
private void unzipStream(String regex, Callback callback)
|
||||
throws IOException {
|
||||
TestUtils.createFile(f1, "one one one");
|
||||
TestUtils.createFile(f2, "two two two");
|
||||
TestUtils.createFile(f3, "three three three");
|
||||
File src = new File(testDir, "abc");
|
||||
File dest = new File(testDir, "dest");
|
||||
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(dest));
|
||||
ZipUtils.copyToZipRecursively(src.getName(), src, zip, null);
|
||||
zip.flush();
|
||||
zip.close();
|
||||
TestUtils.delete(src);
|
||||
|
||||
InputStream in = new FileInputStream(dest);
|
||||
ZipUtils.unzipStream(in, testDir, regex, callback);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user