Merged prototype-test repo into prototype repo, as a separate Eclipse project.

This commit is contained in:
akwizgran
2012-12-05 20:41:01 +00:00
parent f4f7b96d50
commit fa295da4dd
73 changed files with 11019 additions and 0 deletions

12
briar-tests/.classpath Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="lib" path="libs/hamcrest-core-1.1.jar"/>
<classpathentry kind="lib" path="libs/hamcrest-library-1.1.jar"/>
<classpathentry kind="lib" path="libs/jmock-2.5.1.jar"/>
<classpathentry kind="lib" path="libs/junit-4.9b3.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/briar-core"/>
<classpathentry kind="lib" path="/briar-core/android.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

1
briar-tests/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
bin

17
briar-tests/.project Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>briar-tests</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
briar-tests/src/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build
test.tmp

118
briar-tests/src/build.xml Normal file
View File

@@ -0,0 +1,118 @@
<project name='test' default='test'>
<fileset id='core-jars' dir='../../briar-core/libs'>
<include name='*.jar'/>
</fileset>
<fileset id='test-jars' dir='../libs'>
<include name='*.jar'/>
</fileset>
<path id='android-jar'>
<pathelement location='../../briar-core/android.jar'/>
</path>
<path id='core-classes'>
<pathelement location='../../briar-core/build'/>
</path>
<path id='test-classes'>
<pathelement location='../build'/>
</path>
<target name='clean'>
<delete dir='../../briar-core/build'/>
<delete dir='../build'/>
<delete dir='test.tmp'/>
</target>
<target name='compile'>
<mkdir dir='../../briar-core/build'/>
<javac srcdir='../../briar-core/src'
destdir='../../briar-core/build' source='1.5'
includeantruntime='false' debug='off'>
<classpath>
<fileset refid='core-jars'/>
<path refid='android-jar'/>
<path refid='core-classes'/>
</classpath>
</javac>
<mkdir dir='../build'/>
<javac srcdir='.' destdir='../build' source='1.5'
includeantruntime='false' debug='off'>
<classpath>
<fileset refid='core-jars'/>
<fileset refid='test-jars'/>
<path refid='android-jar'/>
<path refid='core-classes'/>
<path refid='test-classes'/>
</classpath>
</javac>
</target>
<target name='test' depends='compile'>
<junit printsummary='on' fork='yes' forkmode='once'>
<assertions>
<enable/>
</assertions>
<classpath>
<fileset refid='core-jars'/>
<fileset refid='test-jars'/>
<path refid='core-classes'/>
<path refid='test-classes'/>
</classpath>
<jvmarg value='-Djava.library.path=../../briar-core/libs'/>
<test name='net.sf.briar.LockFairnessTest'/>
<test name='net.sf.briar.ProtocolIntegrationTest'/>
<test name='net.sf.briar.crypto.CounterModeTest'/>
<test name='net.sf.briar.crypto.ErasableKeyTest'/>
<test name='net.sf.briar.crypto.KeyAgreementTest'/>
<test name='net.sf.briar.crypto.KeyDerivationTest'/>
<test name='net.sf.briar.db.BasicH2Test'/>
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
<test name='net.sf.briar.db.DatabaseComponentImplTest'/>
<test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/>
<test name='net.sf.briar.lifecycle.WindowsShutdownManagerImplTest'/>
<test name='net.sf.briar.plugins.PluginManagerImplTest'/>
<test name='net.sf.briar.plugins.file.LinuxRemovableDriveFinderTest'/>
<test name='net.sf.briar.plugins.file.MacRemovableDriveFinderTest'/>
<test name='net.sf.briar.plugins.file.PollingRemovableDriveMonitorTest'/>
<test name='net.sf.briar.plugins.file.RemovableDrivePluginTest'/>
<test name='net.sf.briar.plugins.file.UnixRemovableDriveMonitorTest'/>
<test name='net.sf.briar.plugins.tcp.LanTcpPluginTest'/>
<test name='net.sf.briar.protocol.AckReaderTest'/>
<test name='net.sf.briar.protocol.BatchReaderTest'/>
<test name='net.sf.briar.protocol.ConstantsTest'/>
<test name='net.sf.briar.protocol.ConsumersTest'/>
<test name='net.sf.briar.protocol.OfferReaderTest'/>
<test name='net.sf.briar.protocol.ProtocolIntegrationTest'/>
<test name='net.sf.briar.protocol.ProtocolWriterImplTest'/>
<test name='net.sf.briar.protocol.RequestReaderTest'/>
<test name='net.sf.briar.protocol.UnverifiedBatchImplTest'/>
<test name='net.sf.briar.protocol.simplex.OutgoingSimplexConnectionTest'/>
<test name='net.sf.briar.protocol.simplex.SimplexProtocolIntegrationTest'/>
<test name='net.sf.briar.serial.ReaderImplTest'/>
<test name='net.sf.briar.serial.WriterImplTest'/>
<test name='net.sf.briar.transport.ConnectionReaderImplTest'/>
<test name='net.sf.briar.transport.ConnectionRegistryImplTest'/>
<test name='net.sf.briar.transport.ConnectionWindowTest'/>
<test name='net.sf.briar.transport.ConnectionWriterImplTest'/>
<test name='net.sf.briar.transport.IncomingEncryptionLayerTest'/>
<test name='net.sf.briar.transport.OutgoingEncryptionLayerTest'/>
<test name='net.sf.briar.transport.TransportIntegrationTest'/>
<test name='net.sf.briar.transport.TransportConnectionRecogniserTest'/>
<test name='net.sf.briar.util.ByteUtilsTest'/>
<test name='net.sf.briar.util.FileUtilsTest'/>
<test name='net.sf.briar.util.StringUtilsTest'/>
<test name='net.sf.briar.util.ZipUtilsTest'/>
</junit>
</target>
<target name='test-slow' depends='compile'>
<junit printsummary='withOutAndErr' fork='yes' forkmode='once'>
<assertions>
<enable/>
</assertions>
<classpath>
<fileset refid='core-jars'/>
<fileset refid='test-jars'/>
<path refid='core-classes'/>
<path refid='test-classes'/>
</classpath>
<jvmarg value='-Djava.library.path=../../briar-core/libs'/>
<test name='net.sf.briar.db.H2DatabaseTest'/>
<test name='net.sf.briar.plugins.tor.TorPluginTest'/>
</junit>
</target>
</project>

View File

@@ -0,0 +1,20 @@
package net.sf.briar;
import java.lang.Thread.UncaughtExceptionHandler;
import junit.framework.TestCase;
public abstract class BriarTestCase extends TestCase {
public BriarTestCase() {
super();
// 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);
}
}

View File

@@ -0,0 +1,161 @@
package net.sf.briar;
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));
}
}

View File

@@ -0,0 +1,264 @@
package net.sf.briar;
import static net.sf.briar.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.security.KeyPair;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.Author;
import net.sf.briar.api.protocol.AuthorFactory;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageFactory;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.Offer;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.ProtocolReader;
import net.sf.briar.api.protocol.ProtocolReaderFactory;
import net.sf.briar.api.protocol.ProtocolWriter;
import net.sf.briar.api.protocol.ProtocolWriterFactory;
import net.sf.briar.api.protocol.RawBatch;
import net.sf.briar.api.protocol.Request;
import net.sf.briar.api.protocol.SubscriptionUpdate;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.clock.ClockModule;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.db.DatabaseModule;
import net.sf.briar.lifecycle.LifecycleModule;
import net.sf.briar.protocol.ProtocolModule;
import net.sf.briar.protocol.duplex.DuplexProtocolModule;
import net.sf.briar.protocol.simplex.SimplexProtocolModule;
import net.sf.briar.serial.SerialModule;
import net.sf.briar.transport.TransportModule;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class ProtocolIntegrationTest extends BriarTestCase {
private final BatchId ack = new BatchId(TestUtils.getRandomId());
private final long timestamp = System.currentTimeMillis();
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final ProtocolReaderFactory protocolReaderFactory;
private final ProtocolWriterFactory protocolWriterFactory;
private final PacketFactory packetFactory;
private final CryptoComponent crypto;
private final ContactId contactId;
private final TransportId transportId;
private final byte[] secret;
private final Author author;
private final Group group, group1;
private final Message message, message1, message2, message3;
private final String authorName = "Alice";
private final String subject = "Hello";
private final String messageBody = "Hello world";
private final Collection<Transport> transports;
public ProtocolIntegrationTest() throws Exception {
super();
Injector i = Guice.createInjector(new ClockModule(), new CryptoModule(),
new DatabaseModule(), new LifecycleModule(),
new ProtocolModule(), new SerialModule(),
new TestDatabaseModule(), new SimplexProtocolModule(),
new TransportModule(), new DuplexProtocolModule());
connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class);
connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
protocolReaderFactory = i.getInstance(ProtocolReaderFactory.class);
protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class);
packetFactory = i.getInstance(PacketFactory.class);
crypto = i.getInstance(CryptoComponent.class);
contactId = new ContactId(234);
transportId = new TransportId(TestUtils.getRandomId());
// Create a shared secret
Random r = new Random();
secret = new byte[32];
r.nextBytes(secret);
// Create two groups: one restricted, one unrestricted
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
group = groupFactory.createGroup("Unrestricted group", null);
KeyPair groupKeyPair = crypto.generateSignatureKeyPair();
group1 = groupFactory.createGroup("Restricted group",
groupKeyPair.getPublic().getEncoded());
// Create an author
AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
author = authorFactory.createAuthor(authorName,
authorKeyPair.getPublic().getEncoded());
// Create two messages to each group: one anonymous, one pseudonymous
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
message = messageFactory.createMessage(null, group, subject,
messageBody.getBytes("UTF-8"));
message1 = messageFactory.createMessage(null, group1,
groupKeyPair.getPrivate(), subject,
messageBody.getBytes("UTF-8"));
message2 = messageFactory.createMessage(null, group, author,
authorKeyPair.getPrivate(), subject,
messageBody.getBytes("UTF-8"));
message3 = messageFactory.createMessage(null, group1,
groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
subject, messageBody.getBytes("UTF-8"));
// Create some transports
TransportId transportId = new TransportId(TestUtils.getRandomId());
Transport transport = new Transport(transportId,
Collections.singletonMap("bar", "baz"));
transports = Collections.singletonList(transport);
}
@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(), 0L, true);
ConnectionWriter conn = connectionWriterFactory.createConnectionWriter(
out, Long.MAX_VALUE, ctx, false, true);
OutputStream out1 = conn.getOutputStream();
ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out1,
false);
Ack a = packetFactory.createAck(Collections.singletonList(ack));
writer.writeAck(a);
Collection<byte[]> batch = Arrays.asList(message.getSerialised(),
message1.getSerialised(), message2.getSerialised(),
message3.getSerialised());
RawBatch b = packetFactory.createBatch(batch);
writer.writeBatch(b);
Collection<MessageId> offer = Arrays.asList(message.getId(),
message1.getId(), message2.getId(), message3.getId());
Offer o = packetFactory.createOffer(offer);
writer.writeOffer(o);
BitSet requested = new BitSet(4);
requested.set(1);
requested.set(3);
Request r = packetFactory.createRequest(requested, 4);
writer.writeRequest(r);
// Use a LinkedHashMap for predictable iteration order
Map<Group, Long> subs = new LinkedHashMap<Group, Long>();
subs.put(group, 0L);
subs.put(group1, 0L);
SubscriptionUpdate s = packetFactory.createSubscriptionUpdate(
Collections.<GroupId, GroupId>emptyMap(), subs, 0L, timestamp);
writer.writeSubscriptionUpdate(s);
TransportUpdate t = packetFactory.createTransportUpdate(transports,
timestamp);
writer.writeTransportUpdate(t);
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(), 0L, false);
ConnectionReader conn = connectionReaderFactory.createConnectionReader(
in, ctx, true, true);
InputStream in1 = conn.getInputStream();
ProtocolReader reader = protocolReaderFactory.createProtocolReader(in1);
// Read the ack
assertTrue(reader.hasAck());
Ack a = reader.readAck();
assertEquals(Collections.singletonList(ack), a.getBatchIds());
// Read and verify the batch
assertTrue(reader.hasBatch());
Batch b = reader.readBatch().verify();
Collection<Message> messages = b.getMessages();
assertEquals(4, messages.size());
Iterator<Message> it = messages.iterator();
checkMessageEquality(message, it.next());
checkMessageEquality(message1, it.next());
checkMessageEquality(message2, it.next());
checkMessageEquality(message3, it.next());
// Read the offer
assertTrue(reader.hasOffer());
Offer o = reader.readOffer();
Collection<MessageId> offered = o.getMessageIds();
assertEquals(4, offered.size());
Iterator<MessageId> it1 = offered.iterator();
assertEquals(message.getId(), it1.next());
assertEquals(message1.getId(), it1.next());
assertEquals(message2.getId(), it1.next());
assertEquals(message3.getId(), it1.next());
// Read the request
assertTrue(reader.hasRequest());
Request req = reader.readRequest();
BitSet requested = req.getBitmap();
assertFalse(requested.get(0));
assertTrue(requested.get(1));
assertFalse(requested.get(2));
assertTrue(requested.get(3));
// If there are any padding bits, they should all be zero
assertEquals(2, requested.cardinality());
// Read the subscription update
assertTrue(reader.hasSubscriptionUpdate());
SubscriptionUpdate s = reader.readSubscriptionUpdate();
Map<Group, Long> subs = s.getSubscriptions();
assertEquals(2, subs.size());
assertEquals(Long.valueOf(0L), subs.get(group));
assertEquals(Long.valueOf(0L), subs.get(group1));
assertTrue(s.getTimestamp() == timestamp);
// Read the transport update
assertTrue(reader.hasTransportUpdate());
TransportUpdate t = reader.readTransportUpdate();
assertEquals(transports, t.getTransports());
assertTrue(t.getTimestamp() == timestamp);
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());
}
}

View File

@@ -0,0 +1,33 @@
package net.sf.briar;
import java.io.File;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseConfig;
public class TestDatabaseConfig implements DatabaseConfig {
private final File dir;
private final long maxSize;
public TestDatabaseConfig(File dir, long maxSize) {
this.dir = dir;
this.maxSize = maxSize;
}
public File getDataDirectory() {
return dir;
}
public Password getPassword() {
return new Password() {
public char[] getPassword() {
return "foo bar".toCharArray();
}
};
}
public long getMaxSize() {
return maxSize;
}
}

View File

@@ -0,0 +1,29 @@
package net.sf.briar;
import java.io.File;
import net.sf.briar.api.db.DatabaseConfig;
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);
}
@Override
protected void configure() {
bind(DatabaseConfig.class).toInstance(config);
}
}

View File

@@ -0,0 +1,76 @@
package net.sf.briar;
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 net.sf.briar.api.protocol.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);
}
}

View File

@@ -0,0 +1,156 @@
package net.sf.briar.crypto;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.Bytes;
import org.junit.Test;
import org.spongycastle.jce.provider.BouncyCastleProvider;
public class CounterModeTest extends BriarTestCase {
private static final String CIPHER_ALGO = "AES";
private static final String CIPHER_MODE = "AES/CTR/NoPadding";
private static final String PROVIDER = "SC";
private static final int KEY_SIZE_BYTES = 32; // AES-256
private static final int BLOCK_SIZE_BYTES = 16;
private final SecureRandom random;
private final byte[] keyBytes;
private final SecretKeySpec key;
public CounterModeTest() {
super();
Security.addProvider(new BouncyCastleProvider());
random = new SecureRandom();
keyBytes = new byte[KEY_SIZE_BYTES];
random.nextBytes(keyBytes);
key = new SecretKeySpec(keyBytes, CIPHER_ALGO);
}
@Test
public void testEveryBitOfIvIsSignificant()
throws GeneralSecurityException {
// Set each bit of the IV in turn, encrypt the same plaintext and check
// that all the resulting ciphertexts are distinct
byte[] plaintext = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(plaintext);
Set<Bytes> ciphertexts = new HashSet<Bytes>();
for(int i = 0; i < BLOCK_SIZE_BYTES * 8; i++) {
// Set the i^th bit of the IV
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
ivBytes[i / 8] |= (byte) (128 >> i % 8);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// Encrypt the plaintext
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext =
new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
ciphertexts.add(new Bytes(ciphertext));
}
// All the ciphertexts should be distinct using Arrays.equals()
assertEquals(BLOCK_SIZE_BYTES * 8, ciphertexts.size());
}
@Test
public void testRepeatedIvsProduceRepeatedCiphertexts()
throws GeneralSecurityException {
// This is the inverse of the previous test, to check that the
// distinct ciphertexts were due to using distinct IVs
byte[] plaintext = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(plaintext);
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Set<Bytes> ciphertexts = new HashSet<Bytes>();
for(int i = 0; i < BLOCK_SIZE_BYTES * 8; i++) {
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext =
new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
ciphertexts.add(new Bytes(ciphertext));
}
assertEquals(1, ciphertexts.size());
}
@Test
public void testLeastSignificantBitsUsedAsCounter()
throws GeneralSecurityException {
// Initialise the least significant 16 bits of the IV to zero and
// encrypt ten blocks of zeroes
byte[] plaintext = new byte[BLOCK_SIZE_BYTES * 10];
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(ivBytes);
ivBytes[BLOCK_SIZE_BYTES - 2] = 0;
ivBytes[BLOCK_SIZE_BYTES - 1] = 0;
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
// Make sure the IV array hasn't been modified
assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 2]);
assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 1]);
// Initialise the least significant 16 bits of the IV to one and
// encrypt another ten blocks of zeroes
ivBytes[BLOCK_SIZE_BYTES - 1] = 1;
iv = new IvParameterSpec(ivBytes);
cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1);
// The last nine blocks of the first ciphertext should be identical to
// the first nine blocks of the second ciphertext
for(int i = 0; i < BLOCK_SIZE_BYTES * 9; i++) {
assertEquals(ciphertext[i + BLOCK_SIZE_BYTES], ciphertext1[i]);
}
}
@Test
public void testCounterUsesMoreThan16Bits()
throws GeneralSecurityException {
// Initialise the least significant bits of the IV to 2^16-1 and
// encrypt ten blocks of zeroes
byte[] plaintext = new byte[BLOCK_SIZE_BYTES * 10];
byte[] ivBytes = new byte[BLOCK_SIZE_BYTES];
random.nextBytes(ivBytes);
ivBytes[BLOCK_SIZE_BYTES - 3] = 0;
ivBytes[BLOCK_SIZE_BYTES - 2] = (byte) 255;
ivBytes[BLOCK_SIZE_BYTES - 1] = (byte) 255;
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
// Make sure the IV array hasn't been modified
assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 3]);
assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 2]);
assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 1]);
// Initialise the least significant bits of the IV to 2^16 and
// encrypt another ten blocks of zeroes
ivBytes[BLOCK_SIZE_BYTES - 3] = 1;
ivBytes[BLOCK_SIZE_BYTES - 2] = 0;
ivBytes[BLOCK_SIZE_BYTES - 1] = 0;
iv = new IvParameterSpec(ivBytes);
cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)];
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1);
// The last nine blocks of the first ciphertext should be identical to
// the first nine blocks of the second ciphertext
for(int i = 0; i < BLOCK_SIZE_BYTES * 9; i++) {
assertEquals(ciphertext[i + BLOCK_SIZE_BYTES], ciphertext1[i]);
}
}
}

View File

@@ -0,0 +1,79 @@
package net.sf.briar.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.ErasableKey;
import org.junit.Test;
public class ErasableKeyTest extends BriarTestCase {
private static final String CIPHER = "AES";
private static final String CIPHER_MODE = "AES/CTR/NoPadding";
private static final int IV_BYTES = 16; // 128 bits
private static final int KEY_BYTES = 32; // 256 bits
private static final String MAC = "HMacSHA384";
private final Random random = new Random();
@Test
public void testCopiesAreErased() {
byte[] master = new byte[KEY_BYTES];
random.nextBytes(master);
ErasableKey k = new ErasableKeyImpl(master, CIPHER);
byte[] copy = k.getEncoded();
assertArrayEquals(master, copy);
k.erase();
byte[] blank = new byte[KEY_BYTES];
assertArrayEquals(blank, master);
assertArrayEquals(blank, copy);
}
@Test
public void testErasureDoesNotAffectCipher() throws Exception {
byte[] key = new byte[KEY_BYTES];
random.nextBytes(key);
ErasableKey k = new ErasableKeyImpl(key, CIPHER);
Cipher c = Cipher.getInstance(CIPHER_MODE);
IvParameterSpec iv = new IvParameterSpec(new byte[IV_BYTES]);
c.init(Cipher.ENCRYPT_MODE, k, iv);
// Encrypt a blank plaintext
byte[] plaintext = new byte[123];
byte[] ciphertext = c.doFinal(plaintext);
// Erase the key and encrypt again - erase() was called after doFinal()
k.erase();
byte[] ciphertext1 = c.doFinal(plaintext);
// Encrypt again - this time erase() was called before doFinal()
byte[] ciphertext2 = c.doFinal(plaintext);
// The ciphertexts should match
assertArrayEquals(ciphertext, ciphertext1);
assertArrayEquals(ciphertext, ciphertext2);
}
@Test
public void testErasureDoesNotAffectMac() throws Exception {
byte[] key = new byte[KEY_BYTES];
random.nextBytes(key);
ErasableKey k = new ErasableKeyImpl(key, CIPHER);
Mac m = Mac.getInstance(MAC);
m.init(k);
// Authenticate a blank plaintext
byte[] plaintext = new byte[123];
byte[] mac = m.doFinal(plaintext);
// Erase the key and authenticate again
k.erase();
byte[] mac1 = m.doFinal(plaintext);
// Authenticate again
byte[] mac2 = m.doFinal(plaintext);
// The MACs should match
assertArrayEquals(mac, mac1);
assertArrayEquals(mac, mac2);
}
}

View File

@@ -0,0 +1,25 @@
package net.sf.briar.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.security.KeyPair;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import org.junit.Test;
public class KeyAgreementTest extends BriarTestCase {
@Test
public void testKeyAgreement() throws Exception {
CryptoComponent crypto = new CryptoComponentImpl();
KeyPair a = crypto.generateAgreementKeyPair();
byte[] aPub = a.getPublic().getEncoded();
KeyPair b = crypto.generateAgreementKeyPair();
byte[] bPub = b.getPublic().getEncoded();
byte[] aSecret = crypto.deriveInitialSecret(aPub, b, true);
byte[] bSecret = crypto.deriveInitialSecret(bPub, a, false);
assertArrayEquals(aSecret, bSecret);
}
}

View File

@@ -0,0 +1,76 @@
package net.sf.briar.crypto;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import org.junit.Test;
public class KeyDerivationTest extends BriarTestCase {
private final CryptoComponent crypto;
private final byte[] secret;
public KeyDerivationTest() {
super();
crypto = new CryptoComponentImpl();
secret = new byte[32];
new Random().nextBytes(secret);
}
@Test
public void testKeysAreDistinct() {
List<ErasableKey> keys = new ArrayList<ErasableKey>();
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));
}
}
}
}

View File

@@ -0,0 +1,192 @@
package net.sf.briar.db;
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.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.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 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 testCreateTableAndAddRow() throws Exception {
// Create the table
createTable(connection);
// Generate an ID
byte[] id = new byte[32];
new Random().nextBytes(id);
// Insert the ID and name into the table
addRow(id, "foo");
}
@Test
public void testCreateTableAddAndRetrieveRow() throws Exception {
// Create the table
createTable(connection);
// Generate an ID
byte[] id = new byte[32];
new Random().nextBytes(id);
// Insert the ID and name into the table
addRow(id, "foo");
// Check that the name can be retrieved using the ID
assertEquals("foo", getName(id));
}
@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
addRow(first, "first");
addRow(second, "second");
addRow(third, "third");
addRow(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 addRow(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, Types.BINARY);
else ps.setBytes(1, id);
ps.setString(2, name);
int rowsAffected = ps.executeUpdate();
ps.close();
assertEquals(1, rowsAffected);
} catch(SQLException e) {
connection.close();
throw e;
}
}
private String getName(byte[] id) throws SQLException {
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if(id != null) 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 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);
}
}

View File

@@ -0,0 +1,67 @@
package net.sf.briar.db;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.concurrent.CountDownLatch;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.clock.SystemTimer;
import net.sf.briar.api.clock.Timer;
import net.sf.briar.api.db.DbException;
import net.sf.briar.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, 10L);
// 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, 10L * 1000L);
// 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 < 10L * 1000L);
}
}

View File

@@ -0,0 +1,151 @@
package net.sf.briar.db;
import static net.sf.briar.db.DatabaseConstants.BYTES_PER_SWEEP;
import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE;
import java.util.Collections;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.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);
final PacketFactory packetFactory = context.mock(PacketFactory.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
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);
final PacketFactory packetFactory = context.mock(PacketFactory.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,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testExpiringUnsendableMessageDoesNotTriggerBackwardInclusion()
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);
final PacketFactory packetFactory = context.mock(PacketFactory.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.singletonList(messageId)));
oneOf(database).getSendability(txn, messageId);
will(returnValue(0));
oneOf(database).removeMessage(txn, messageId);
oneOf(database).commitTransaction(txn);
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testExpiringSendableMessageTriggersBackwardInclusion()
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);
final PacketFactory packetFactory = context.mock(PacketFactory.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.singletonList(messageId)));
oneOf(database).getSendability(txn, messageId);
will(returnValue(1));
oneOf(database).getGroupMessageParent(txn, messageId);
will(returnValue(null));
oneOf(database).removeMessage(txn, messageId);
oneOf(database).commitTransaction(txn);
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Override
protected <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner,
ShutdownManager shutdown, PacketFactory packetFactory) {
return createDatabaseComponentImpl(database, cleaner, shutdown,
packetFactory);
}
private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner,
ShutdownManager shutdown, PacketFactory packetFactory) {
return new DatabaseComponentImpl<T>(database, cleaner, shutdown,
packetFactory, new SystemClock());
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
package net.sf.briar.db;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupId;
class TestGroup implements Group {
private final GroupId id;
private final String name;
private final byte[] publicKey;
public TestGroup(GroupId id, String name, byte[] publicKey) {
this.id = id;
this.name = name;
this.publicKey = publicKey;
}
public GroupId getId() {
return id;
}
public String getName() {
return name;
}
public byte[] getPublicKey() {
return publicKey;
}
}

View File

@@ -0,0 +1,20 @@
package net.sf.briar.db;
import java.io.IOException;
import net.sf.briar.TestUtils;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.GroupId;
class TestGroupFactory implements GroupFactory {
public Group createGroup(String name, byte[] publicKey) throws IOException {
GroupId id = new GroupId(TestUtils.getRandomId());
return new TestGroup(id, name, publicKey);
}
public Group createGroup(GroupId id, String name, byte[] publicKey) {
return new TestGroup(id, name, publicKey);
}
}

View File

@@ -0,0 +1,89 @@
package net.sf.briar.db;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId;
class TestMessage implements Message {
private final MessageId id, parent;
private final GroupId group;
private final AuthorId author;
private final String subject;
private final long timestamp;
private final byte[] raw;
private final int bodyStart, bodyLength;
public TestMessage(MessageId id, MessageId parent, GroupId group,
AuthorId author, String subject, long timestamp, byte[] raw) {
this(id, parent, group, author, subject, timestamp, raw, 0, raw.length);
}
public TestMessage(MessageId id, MessageId parent, GroupId group,
AuthorId author, String subject, long timestamp, byte[] raw,
int bodyStart, int bodyLength) {
this.id = id;
this.parent = parent;
this.group = group;
this.author = author;
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 GroupId getGroup() {
return group;
}
public AuthorId getAuthor() {
return author;
}
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 boolean equals(Object o) {
return o instanceof Message && id.equals(((Message)o).getId());
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@@ -0,0 +1,33 @@
package net.sf.briar.lifecycle;
import java.util.HashSet;
import java.util.Set;
import net.sf.briar.BriarTestCase;
import net.sf.briar.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();
}
}

View File

@@ -0,0 +1,39 @@
package net.sf.briar.lifecycle;
import net.sf.briar.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();
}
}
}
}

View File

@@ -0,0 +1,101 @@
package net.sf.briar.plugins;
import java.io.IOException;
import java.util.Map;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.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");
plugin.start();
// Try to connect to the server
System.out.println("Creating connection");
DuplexTransportConnection d = plugin.createConnection(contactId);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
receiveChallengeSendResponse(d);
}
// Try to send an invitation
System.out.println("Sending invitation");
d = plugin.sendInvitation(getPseudoRandom(123), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
receiveChallengeSendResponse(d);
}
// Try to accept an invitation
System.out.println("Accepting invitation");
d = plugin.acceptInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
sendChallengeReceiveResponse(d);
}
// 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) {}
}
}

View File

@@ -0,0 +1,103 @@
package net.sf.briar.plugins;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.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");
plugin.start();
// Wait for a connection
System.out.println("Waiting for connection");
callback.latch.await();
// Try to accept an invitation
System.out.println("Accepting invitation");
DuplexTransportConnection d = plugin.acceptInvitation(
getPseudoRandom(123), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
sendChallengeReceiveResponse(d);
}
// Try to send an invitation
System.out.println("Sending invitation");
d = plugin.sendInvitation(getPseudoRandom(456), INVITATION_TIMEOUT);
if(d == null) {
System.out.println("Connection failed");
} else {
System.out.println("Connection created");
receiveChallengeSendResponse(d);
}
// 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) {}
}
}

View File

@@ -0,0 +1,98 @@
package net.sf.briar.plugins;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Random;
import java.util.Scanner;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
abstract class DuplexTest {
protected static final String CHALLENGE = "Carrots!";
protected static final String RESPONSE = "Potatoes!";
protected static final long INVITATION_TIMEOUT = 30 * 1000;
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);
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);
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) {
return new TestPseudoRandom(seed);
}
private static class TestPseudoRandom implements PseudoRandom {
private final Random r;
private TestPseudoRandom(int seed) {
r = new Random(seed);
}
public byte[] nextBytes(int bytes) {
byte[] b = new byte[bytes];
r.nextBytes(b);
return b;
}
}
}

View File

@@ -0,0 +1,10 @@
package net.sf.briar.plugins;
import java.util.concurrent.Executor;
public class ImmediateExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}

View File

@@ -0,0 +1,63 @@
package net.sf.briar.plugins;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.android.AndroidExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionDispatcher;
import net.sf.briar.api.ui.UiCallback;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
public class PluginManagerImplTest extends BriarTestCase {
@SuppressWarnings("unchecked")
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery();
final AndroidExecutor androidExecutor =
context.mock(AndroidExecutor.class);
final ShutdownManager shutdownManager =
context.mock(ShutdownManager.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);
context.checking(new Expectations() {{
// Start
oneOf(poller).start(with(any(Collection.class)));
allowing(db).getConfig(with(any(TransportId.class)));
will(returnValue(new TransportConfig()));
allowing(db).getLocalProperties(with(any(TransportId.class)));
will(returnValue(new TransportProperties()));
allowing(db).getRemoteProperties(with(any(TransportId.class)));
will(returnValue(new TransportProperties()));
allowing(db).mergeLocalProperties(with(any(TransportId.class)),
with(any(TransportProperties.class)));
// Stop
oneOf(poller).stop();
oneOf(androidExecutor).shutdown();
}});
ExecutorService executor = Executors.newCachedThreadPool();
PluginManagerImpl p = new PluginManagerImpl(executor, androidExecutor,
shutdownManager, db, poller, dispatcher, uiCallback);
// We expect either 3 or 4 plugins to be started, depending on whether
// the test machine has a Bluetooth device
int started = p.start(null);
int stopped = p.stop();
assertEquals(started, stopped);
assertTrue(started >= 3);
assertTrue(started <= 4);
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,44 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.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.getUuid());
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(), callback, 0L);
}
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();
}
}
}

View File

@@ -0,0 +1,35 @@
package net.sf.briar.plugins.bluetooth;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.SystemClock;
import net.sf.briar.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.getUuid());
// Create the plugin
callback = new ServerCallback(new TransportConfig(), local,
Collections.singletonMap(contactId, new TransportProperties()));
plugin = new BluetoothPlugin(executor, new SystemClock(), callback, 0L);
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
try {
new BluetoothServerTest(executor).run();
} finally {
executor.shutdown();
}
}
}

View File

@@ -0,0 +1,13 @@
package net.sf.briar.plugins.bluetooth;
import java.util.UUID;
class BluetoothTest {
private static final String EMPTY_UUID =
UUID.nameUUIDFromBytes(new byte[0]).toString().replaceAll("-", "");
static String getUuid() {
return EMPTY_UUID;
}
}

View File

@@ -0,0 +1,25 @@
package net.sf.briar.plugins.file;
import net.sf.briar.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));
}
}

View File

@@ -0,0 +1,23 @@
package net.sf.briar.plugins.file;
import net.sf.briar.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));
}
}

View File

@@ -0,0 +1,95 @@
package net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.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();
}
}

View File

@@ -0,0 +1,355 @@
package net.sf.briar.plugins.file;
import static net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.plugins.simplex.SimplexPluginCallback;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
import net.sf.briar.plugins.ImmediateExecutor;
import net.sf.briar.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);
@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,
callback, finder, monitor);
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,
callback, finder, monitor);
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,
callback, finder, monitor);
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,
callback, finder, monitor);
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,
callback, finder, monitor);
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(0L, 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,
callback, finder, monitor);
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(0L, files[0].length());
// Writing to the output stream should increase the size of the file
OutputStream out = writer.getOutputStream();
out.write(new byte[123]);
out.flush();
out.close();
// Disposing of the writer should not delete the file
writer.dispose(false);
assertTrue(files[0].exists());
assertEquals(123L, 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,
callback, finder, monitor);
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,
callback, finder, monitor);
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(), callback, finder, monitor);
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);
}
}

View File

@@ -0,0 +1,100 @@
package net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.plugins.file.RemovableDriveMonitor.Callback;
import net.sf.briar.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() };
}
};
}
}

View File

@@ -0,0 +1,45 @@
package net.sf.briar.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 net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.plugins.DuplexClientTest;
import net.sf.briar.plugins.tcp.LanTcpPlugin;
// 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);
plugin = new LanTcpPlugin(executor, callback, 0L);
}
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();
}
}
}

View File

@@ -0,0 +1,142 @@
package net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import net.sf.briar.plugins.tcp.LanTcpPlugin;
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 e = Executors.newCachedThreadPool();
DuplexPlugin plugin = new LanTcpPlugin(e, callback, 0L);
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.valueOf(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 e = Executors.newCachedThreadPool();
DuplexPlugin plugin = new LanTcpPlugin(e, callback, 0L);
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) {}
}
}

View File

@@ -0,0 +1,32 @@
package net.sf.briar.plugins.tcp;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.plugins.DuplexServerTest;
import net.sf.briar.plugins.tcp.LanTcpPlugin;
// 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()));
plugin = new LanTcpPlugin(executor, callback, 0L);
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
try {
new LanTcpServerTest(executor).run();
} finally {
executor.shutdown();
}
}
}

View File

@@ -0,0 +1,175 @@
package net.sf.briar.plugins.tor;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.io.PrintStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
import org.junit.Test;
public class TorPluginTest extends BriarTestCase {
private final ContactId contactId = new ContactId(234);
@Test
public void testHiddenService() throws Exception {
System.err.println("======== testHiddenService ========");
Executor e = Executors.newCachedThreadPool();
TorPlugin serverPlugin = null, clientPlugin = null;
try {
// Create a plugin instance for the server
Callback serverCallback = new Callback();
serverPlugin = new TorPlugin(e, serverCallback, 0L);
System.out.println("Starting server plugin");
serverPlugin.start();
// The plugin should create a hidden service... eventually
assertTrue(serverCallback.latch.await(600, SECONDS));
System.out.println("Started server plugin");
String onion = serverCallback.local.get("onion");
assertNotNull(onion);
assertTrue(onion.endsWith(".onion"));
// Create another plugin instance for the client
Callback clientCallback = new Callback();
clientCallback.config.put("noHiddenService", "");
TransportProperties p = new TransportProperties();
p.put("onion", onion);
clientCallback.remote.put(contactId, p);
clientPlugin = new TorPlugin(e, clientCallback, 0L);
System.out.println("Starting client plugin");
clientPlugin.start();
// The plugin should start without creating a hidden service
assertTrue(clientCallback.latch.await(600, SECONDS));
System.out.println("Started client plugin");
// Connect to the server's hidden service
System.out.println("Connecting to hidden service");
DuplexTransportConnection clientEnd =
clientPlugin.createConnection(contactId);
assertNotNull(clientEnd);
DuplexTransportConnection serverEnd =
serverCallback.incomingConnection;
assertNotNull(serverEnd);
System.out.println("Connected to hidden service");
// Send some data through the Tor connection
PrintStream out = new PrintStream(clientEnd.getOutputStream());
out.println("Hello world");
out.flush();
Scanner in = new Scanner(serverEnd.getInputStream());
assertTrue(in.hasNextLine());
assertEquals("Hello world", in.nextLine());
serverEnd.dispose(false, false);
clientEnd.dispose(false, false);
} finally {
// Stop the plugins
System.out.println("Stopping plugins");
if(serverPlugin != null) serverPlugin.stop();
if(clientPlugin != null) clientPlugin.stop();
System.out.println("Stopped plugins");
}
}
@Test
public void testStoreAndRetrievePrivateKey() throws Exception {
System.err.println("======== testStoreAndRetrievePrivateKey ========");
Executor e = Executors.newCachedThreadPool();
TorPlugin plugin = null;
try {
// Start a plugin instance with no private key
Callback callback = new Callback();
plugin = new TorPlugin(e, callback, 0L);
System.out.println("Starting plugin without private key");
plugin.start();
// The plugin should create a hidden service... eventually
assertTrue(callback.latch.await(600, SECONDS));
System.out.println("Started plugin");
String onion = callback.local.get("onion");
assertNotNull(onion);
assertTrue(onion.endsWith(".onion"));
// Get the PEM-encoded private key
String privateKey = callback.config.get("privateKey");
assertNotNull(privateKey);
// Stop the plugin
System.out.println("Stopping plugin");
plugin.stop();
System.out.println("Stopped plugin");
// Start another instance, reusing the private key
callback = new Callback();
callback.config.put("privateKey", privateKey);
plugin = new TorPlugin(e, callback, 0L);
System.out.println("Starting plugin with private key");
plugin.start();
// The plugin should create a hidden service... eventually
assertTrue(callback.latch.await(600, SECONDS));
System.out.println("Started plugin");
// The onion URL should be the same
assertEquals(onion, callback.local.get("onion"));
// The private key should be the same
assertEquals(privateKey, callback.config.get("privateKey"));
} finally {
// Stop the plugin
System.out.println("Stopping plugin");
if(plugin != null) plugin.stop();
System.out.println("Stopped plugin");
}
}
private static class Callback implements DuplexPluginCallback {
private final Map<ContactId, TransportProperties> remote =
new Hashtable<ContactId, TransportProperties>();
private final CountDownLatch latch = new CountDownLatch(1);
private final TransportConfig config = new TransportConfig();
private final TransportProperties local = new TransportProperties();
private volatile DuplexTransportConnection incomingConnection = null;
public TransportConfig getConfig() {
return config;
}
public TransportProperties getLocalProperties() {
return local;
}
public Map<ContactId, TransportProperties> getRemoteProperties() {
return remote;
}
public void mergeConfig(TransportConfig c) {
config.putAll(c);
}
public void mergeLocalProperties(TransportProperties p) {
local.putAll(p);
latch.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) {
incomingConnection = d;
}
public void outgoingConnectionCreated(ContactId c,
DuplexTransportConnection d) {}
}
}

View File

@@ -0,0 +1,124 @@
package net.sf.briar.protocol;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Collection;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.Types;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.SerialComponent;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.SerialModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class AckReaderTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private final SerialComponent serial;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final Mockery context;
public AckReaderTest() throws Exception {
super();
Injector i = Guice.createInjector(new SerialModule());
serial = i.getInstance(SerialComponent.class);
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
context = new Mockery();
}
@Test
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
PacketFactory packetFactory = context.mock(PacketFactory.class);
AckReader ackReader = new AckReader(packetFactory);
byte[] b = createAck(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.ACK, ackReader);
try {
reader.readStruct(Types.ACK, Ack.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
@Test
@SuppressWarnings("unchecked")
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
final PacketFactory packetFactory = context.mock(PacketFactory.class);
AckReader ackReader = new AckReader(packetFactory);
final Ack ack = context.mock(Ack.class);
context.checking(new Expectations() {{
oneOf(packetFactory).createAck(with(any(Collection.class)));
will(returnValue(ack));
}});
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.ACK, ackReader);
assertEquals(ack, reader.readStruct(Types.ACK, Ack.class));
context.assertIsSatisfied();
}
@Test
public void testEmptyAck() throws Exception {
final PacketFactory packetFactory = context.mock(PacketFactory.class);
AckReader ackReader = new AckReader(packetFactory);
byte[] b = createEmptyAck();
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.ACK, ackReader);
try {
reader.readStruct(Types.ACK, Ack.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
private byte[] createAck(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(Types.ACK);
w.writeListStart();
while(out.size() + serial.getSerialisedUniqueIdLength()
< MAX_PACKET_LENGTH) {
w.writeBytes(TestUtils.getRandomId());
}
if(tooBig) w.writeBytes(TestUtils.getRandomId());
w.writeListEnd();
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.writeStructId(Types.ACK);
w.writeListStart();
w.writeListEnd();
return out.toByteArray();
}
}

View File

@@ -0,0 +1,137 @@
package net.sf.briar.protocol;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.protocol.Types;
import net.sf.briar.api.protocol.UnverifiedBatch;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.StructReader;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.SerialModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class BatchReaderTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final Mockery context;
private final UnverifiedMessage message;
private final StructReader<UnverifiedMessage> messageReader;
public BatchReaderTest() throws Exception {
super();
Injector i = Guice.createInjector(new SerialModule());
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
context = new Mockery();
message = context.mock(UnverifiedMessage.class);
messageReader = new TestMessageReader();
}
@Test
public void testFormatExceptionIfBatchIsTooLarge() throws Exception {
UnverifiedBatchFactory batchFactory =
context.mock(UnverifiedBatchFactory.class);
BatchReader batchReader = new BatchReader(messageReader, batchFactory);
byte[] b = createBatch(MAX_PACKET_LENGTH + 1);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.BATCH, batchReader);
try {
reader.readStruct(Types.BATCH, UnverifiedBatch.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
@Test
public void testNoFormatExceptionIfBatchIsMaximumSize() throws Exception {
final UnverifiedBatchFactory batchFactory =
context.mock(UnverifiedBatchFactory.class);
BatchReader batchReader = new BatchReader(messageReader, batchFactory);
final UnverifiedBatch batch = context.mock(UnverifiedBatch.class);
context.checking(new Expectations() {{
oneOf(batchFactory).createUnverifiedBatch(
Collections.singletonList(message));
will(returnValue(batch));
}});
byte[] b = createBatch(MAX_PACKET_LENGTH);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.BATCH, batchReader);
assertEquals(batch, reader.readStruct(Types.BATCH,
UnverifiedBatch.class));
context.assertIsSatisfied();
}
@Test
public void testEmptyBatch() throws Exception {
final UnverifiedBatchFactory batchFactory =
context.mock(UnverifiedBatchFactory.class);
BatchReader batchReader = new BatchReader(messageReader, batchFactory);
byte[] b = createEmptyBatch();
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.BATCH, batchReader);
try {
reader.readStruct(Types.BATCH, UnverifiedBatch.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
private byte[] createBatch(int size) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(size);
Writer w = writerFactory.createWriter(out);
w.writeStructId(Types.BATCH);
w.writeListStart();
// We're using a fake message reader, so it's OK to use a fake message
w.writeStructId(Types.MESSAGE);
w.writeBytes(new byte[size - 10]);
w.writeListEnd();
byte[] b = out.toByteArray();
assertEquals(size, b.length);
return b;
}
private byte[] createEmptyBatch() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(Types.BATCH);
w.writeListStart();
w.writeListEnd();
return out.toByteArray();
}
private class TestMessageReader implements StructReader<UnverifiedMessage> {
public UnverifiedMessage readStruct(Reader r) throws IOException {
r.readStructId(Types.MESSAGE);
r.readBytes();
return message;
}
}
}

View File

@@ -0,0 +1,193 @@
package net.sf.briar.protocol;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_AUTHOR_NAME_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_BODY_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_GROUP_NAME_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PUBLIC_KEY_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_SUBJECT_LENGTH;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_TRANSPORTS;
import java.io.ByteArrayOutputStream;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.Author;
import net.sf.briar.api.protocol.AuthorFactory;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageFactory;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.Offer;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.ProtocolWriter;
import net.sf.briar.api.protocol.ProtocolWriterFactory;
import net.sf.briar.api.protocol.RawBatch;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.serial.SerialModule;
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 PacketFactory packetFactory;
private final ProtocolWriterFactory protocolWriterFactory;
public ConstantsTest() throws Exception {
super();
Injector i = Guice.createInjector(new CryptoModule(),
new ProtocolModule(), new SerialModule());
crypto = i.getInstance(CryptoComponent.class);
groupFactory = i.getInstance(GroupFactory.class);
authorFactory = i.getInstance(AuthorFactory.class);
messageFactory = i.getInstance(MessageFactory.class);
packetFactory = i.getInstance(PacketFactory.class);
protocolWriterFactory = i.getInstance(ProtocolWriterFactory.class);
}
@Test
public void testBatchesFitIntoLargeAck() throws Exception {
testBatchesFitIntoAck(MAX_PACKET_LENGTH);
}
@Test
public void testBatchesFitIntoSmallAck() throws Exception {
testBatchesFitIntoAck(1000);
}
private void testBatchesFitIntoAck(int length) throws Exception {
// Create an ack with as many batch IDs as possible
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out,
true);
int maxBatches = writer.getMaxBatchesForAck(length);
Collection<BatchId> acked = new ArrayList<BatchId>();
for(int i = 0; i < maxBatches; i++) {
acked.add(new BatchId(TestUtils.getRandomId()));
}
Ack a = packetFactory.createAck(acked);
writer.writeAck(a);
// Check the size of the serialised ack
assertTrue(out.size() <= length);
}
@Test
public void testMessageFitsIntoBatch() throws Exception {
// Create a maximum-length group
String groupName = createRandomString(MAX_GROUP_NAME_LENGTH);
byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
Group group = groupFactory.createGroup(groupName, groupPublic);
// Create a maximum-length author
String authorName = 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 groupPrivate = crypto.generateSignatureKeyPair().getPrivate();
PrivateKey authorPrivate = crypto.generateSignatureKeyPair().getPrivate();
String subject = createRandomString(MAX_SUBJECT_LENGTH);
byte[] body = new byte[MAX_BODY_LENGTH];
Message message = messageFactory.createMessage(null, group,
groupPrivate, author, authorPrivate, subject, body);
// Add the message to a batch
ByteArrayOutputStream out =
new ByteArrayOutputStream(MAX_PACKET_LENGTH);
ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out,
true);
RawBatch b = packetFactory.createBatch(Collections.singletonList(
message.getSerialised()));
writer.writeBatch(b);
// Check the size of the serialised batch
assertTrue(out.size() > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH
+ MAX_PUBLIC_KEY_LENGTH + MAX_AUTHOR_NAME_LENGTH
+ MAX_PUBLIC_KEY_LENGTH + MAX_BODY_LENGTH);
assertTrue(out.size() <= MAX_PACKET_LENGTH);
}
@Test
public void testMessagesFitIntoLargeOffer() throws Exception {
testMessagesFitIntoOffer(MAX_PACKET_LENGTH);
}
@Test
public void testMessagesFitIntoSmallOffer() throws Exception {
testMessagesFitIntoOffer(1000);
}
private void testMessagesFitIntoOffer(int length) throws Exception {
// Create an offer with as many message IDs as possible
ByteArrayOutputStream out = new ByteArrayOutputStream(length);
ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(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()));
}
Offer o = packetFactory.createOffer(offered);
writer.writeOffer(o);
// Check the size of the serialised offer
assertTrue(out.size() <= length);
}
@Test
public void testTransportsFitIntoUpdate() throws Exception {
// Create the maximum number of plugins, each with the maximum number
// of maximum-length properties
Collection<Transport> transports = new ArrayList<Transport>();
for(int i = 0; i < MAX_TRANSPORTS; i++) {
TransportId id = new TransportId(TestUtils.getRandomId());
Transport t = new Transport(id);
for(int j = 0; j < MAX_PROPERTIES_PER_TRANSPORT; j++) {
String key = createRandomString(MAX_PROPERTY_LENGTH);
String value = createRandomString(MAX_PROPERTY_LENGTH);
t.put(key, value);
}
transports.add(t);
}
// Add the transports to an update
ByteArrayOutputStream out =
new ByteArrayOutputStream(MAX_PACKET_LENGTH);
ProtocolWriter writer = protocolWriterFactory.createProtocolWriter(out,
true);
TransportUpdate t = packetFactory.createTransportUpdate(transports,
Long.MAX_VALUE);
writer.writeTransportUpdate(t);
// Check the size of the serialised update
assertTrue(out.size() > MAX_TRANSPORTS * (UniqueId.LENGTH + 4
+ (MAX_PROPERTIES_PER_TRANSPORT * MAX_PROPERTY_LENGTH * 2))
+ 8);
assertTrue(out.size() <= MAX_PACKET_LENGTH);
}
private static String createRandomString(int length) throws Exception {
StringBuilder s = new StringBuilder(length);
for(int i = 0; i < length; i++) {
int digit = (int) (Math.random() * 10);
s.append((char) ('0' + digit));
}
String string = s.toString();
assertEquals(length, string.getBytes("UTF-8").length);
return string;
}
}

View File

@@ -0,0 +1,105 @@
package net.sf.briar.protocol;
import static org.junit.Assert.assertArrayEquals;
import java.security.GeneralSecurityException;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.serial.CopyingConsumer;
import net.sf.briar.api.serial.CountingConsumer;
import net.sf.briar.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-256");
}
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);
}
}
}

View File

@@ -0,0 +1,124 @@
package net.sf.briar.protocol;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Collection;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.protocol.Offer;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.Types;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.SerialComponent;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.serial.SerialModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class OfferReaderTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private final SerialComponent serial;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final Mockery context;
public OfferReaderTest() throws Exception {
super();
Injector i = Guice.createInjector(new SerialModule());
serial = i.getInstance(SerialComponent.class);
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
context = new Mockery();
}
@Test
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
PacketFactory packetFactory = context.mock(PacketFactory.class);
OfferReader offerReader = new OfferReader(packetFactory);
byte[] b = createOffer(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.OFFER, offerReader);
try {
reader.readStruct(Types.OFFER, Offer.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
@Test
@SuppressWarnings("unchecked")
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
final PacketFactory packetFactory = context.mock(PacketFactory.class);
OfferReader offerReader = new OfferReader(packetFactory);
final Offer offer = context.mock(Offer.class);
context.checking(new Expectations() {{
oneOf(packetFactory).createOffer(with(any(Collection.class)));
will(returnValue(offer));
}});
byte[] b = createOffer(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.OFFER, offerReader);
assertEquals(offer, reader.readStruct(Types.OFFER, Offer.class));
context.assertIsSatisfied();
}
@Test
public void testEmptyOffer() throws Exception {
final PacketFactory packetFactory = context.mock(PacketFactory.class);
OfferReader offerReader = new OfferReader(packetFactory);
byte[] b = createEmptyOffer();
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.OFFER, offerReader);
try {
reader.readStruct(Types.OFFER, Offer.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
private byte[] createOffer(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(Types.OFFER);
w.writeListStart();
while(out.size() + serial.getSerialisedUniqueIdLength()
< MAX_PACKET_LENGTH) {
w.writeBytes(TestUtils.getRandomId());
}
if(tooBig) w.writeBytes(TestUtils.getRandomId());
w.writeListEnd();
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.writeStructId(Types.OFFER);
w.writeListStart();
w.writeListEnd();
return out.toByteArray();
}
}

View File

@@ -0,0 +1,133 @@
package net.sf.briar.protocol;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageFactory;
import net.sf.briar.api.protocol.Offer;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.ProtocolReader;
import net.sf.briar.api.protocol.ProtocolReaderFactory;
import net.sf.briar.api.protocol.ProtocolWriter;
import net.sf.briar.api.protocol.ProtocolWriterFactory;
import net.sf.briar.api.protocol.RawBatch;
import net.sf.briar.api.protocol.Request;
import net.sf.briar.api.protocol.SubscriptionUpdate;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.serial.SerialModule;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class ProtocolIntegrationTest extends BriarTestCase {
private final ProtocolReaderFactory readerFactory;
private final ProtocolWriterFactory writerFactory;
private final PacketFactory packetFactory;
private final BatchId batchId;
private final Group group;
private final Message message;
private final String subject = "Hello";
private final String messageBody = "Hello world";
private final BitSet bitSet;
private final Map<Group, Long> subscriptions;
private final Collection<Transport> transports;
private final long timestamp = System.currentTimeMillis();
public ProtocolIntegrationTest() throws Exception {
super();
Injector i = Guice.createInjector(new CryptoModule(),
new ProtocolModule(), new SerialModule());
readerFactory = i.getInstance(ProtocolReaderFactory.class);
writerFactory = i.getInstance(ProtocolWriterFactory.class);
packetFactory = i.getInstance(PacketFactory.class);
batchId = new BatchId(TestUtils.getRandomId());
GroupFactory groupFactory = i.getInstance(GroupFactory.class);
group = groupFactory.createGroup("Unrestricted group", null);
MessageFactory messageFactory = i.getInstance(MessageFactory.class);
message = messageFactory.createMessage(null, group, subject,
messageBody.getBytes("UTF-8"));
bitSet = new BitSet();
bitSet.set(3);
bitSet.set(7);
subscriptions = Collections.singletonMap(group, 123L);
TransportId transportId = new TransportId(TestUtils.getRandomId());
Transport transport = new Transport(transportId,
Collections.singletonMap("bar", "baz"));
transports = Collections.singletonList(transport);
}
@Test
public void testWriteAndRead() throws Exception {
// Write
ByteArrayOutputStream out = new ByteArrayOutputStream();
ProtocolWriter writer = writerFactory.createProtocolWriter(out, true);
Ack a = packetFactory.createAck(Collections.singletonList(batchId));
writer.writeAck(a);
RawBatch b = packetFactory.createBatch(Collections.singletonList(
message.getSerialised()));
writer.writeBatch(b);
Offer o = packetFactory.createOffer(Collections.singletonList(
message.getId()));
writer.writeOffer(o);
Request r = packetFactory.createRequest(bitSet, 10);
writer.writeRequest(r);
SubscriptionUpdate s = packetFactory.createSubscriptionUpdate(
Collections.<GroupId, GroupId>emptyMap(), subscriptions, 0L,
timestamp);
writer.writeSubscriptionUpdate(s);
TransportUpdate t = packetFactory.createTransportUpdate(transports,
timestamp);
writer.writeTransportUpdate(t);
// Read
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ProtocolReader reader = readerFactory.createProtocolReader(in);
a = reader.readAck();
assertEquals(Collections.singletonList(batchId), a.getBatchIds());
Batch b1 = reader.readBatch().verify();
assertEquals(Collections.singletonList(message), b1.getMessages());
o = reader.readOffer();
assertEquals(Collections.singletonList(message.getId()),
o.getMessageIds());
r = reader.readRequest();
assertEquals(bitSet, r.getBitmap());
assertEquals(10, r.getLength());
s = reader.readSubscriptionUpdate();
assertEquals(subscriptions, s.getSubscriptions());
assertEquals(timestamp, s.getTimestamp());
t = reader.readTransportUpdate();
assertEquals(transports, t.getTransports());
assertEquals(timestamp, t.getTimestamp());
}
}

View File

@@ -0,0 +1,87 @@
package net.sf.briar.protocol;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.BitSet;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.ProtocolWriter;
import net.sf.briar.api.protocol.Request;
import net.sf.briar.api.serial.SerialComponent;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.serial.SerialModule;
import net.sf.briar.util.StringUtils;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class ProtocolWriterImplTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private final PacketFactory packetFactory;
private final SerialComponent serial;
private final WriterFactory writerFactory;
public ProtocolWriterImplTest() {
super();
Injector i = Guice.createInjector(new CryptoModule(),
new ProtocolModule(), new SerialModule());
packetFactory = i.getInstance(PacketFactory.class);
serial = i.getInstance(SerialComponent.class);
writerFactory = i.getInstance(WriterFactory.class);
}
@Test
public void testWriteBitmapNoPadding() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ProtocolWriter w = new ProtocolWriterImpl(serial, writerFactory, out,
true);
BitSet b = new BitSet();
// 11011001 = 0xD9
b.set(0);
b.set(1);
b.set(3);
b.set(4);
b.set(7);
// 01011001 = 0x59
b.set(9);
b.set(11);
b.set(12);
b.set(15);
Request r = packetFactory.createRequest(b, 16);
w.writeRequest(r);
// Short user tag 6, 0 as uint7, short bytes with length 2, 0xD959
byte[] output = out.toByteArray();
assertEquals("C6" + "00" + "92" + "D959",
StringUtils.toHexString(output));
}
@Test
public void testWriteBitmapWithPadding() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ProtocolWriter w = new ProtocolWriterImpl(serial, writerFactory, out,
true);
BitSet b = new BitSet();
// 01011001 = 0x59
b.set(1);
b.set(3);
b.set(4);
b.set(7);
// 11011xxx = 0xD8, after padding
b.set(8);
b.set(9);
b.set(11);
b.set(12);
Request r = packetFactory.createRequest(b, 13);
w.writeRequest(r);
// Short user tag 6, 3 as uint7, short bytes with length 2, 0x59D8
byte[] output = out.toByteArray();
assertEquals("C6" + "03" + "92" + "59D8",
StringUtils.toHexString(output));
}
}

View File

@@ -0,0 +1,146 @@
package net.sf.briar.protocol;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.BitSet;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.protocol.PacketFactory;
import net.sf.briar.api.protocol.Request;
import net.sf.briar.api.protocol.Types;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.api.serial.ReaderFactory;
import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.serial.SerialModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class RequestReaderTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final PacketFactory packetFactory;
private final Mockery context;
public RequestReaderTest() throws Exception {
super();
Injector i = Guice.createInjector(new CryptoModule(),
new ProtocolModule(), new SerialModule());
readerFactory = i.getInstance(ReaderFactory.class);
writerFactory = i.getInstance(WriterFactory.class);
packetFactory = i.getInstance(PacketFactory.class);
context = new Mockery();
}
@Test
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
PacketFactory packetFactory = context.mock(PacketFactory.class);
RequestReader requestReader = new RequestReader(packetFactory);
byte[] b = createRequest(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.REQUEST, requestReader);
try {
reader.readStruct(Types.REQUEST, Request.class);
fail();
} catch(FormatException expected) {}
context.assertIsSatisfied();
}
@Test
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
final PacketFactory packetFactory = context.mock(PacketFactory.class);
RequestReader requestReader = new RequestReader(packetFactory);
final Request request = context.mock(Request.class);
context.checking(new Expectations() {{
oneOf(packetFactory).createRequest(with(any(BitSet.class)),
with(any(int.class)));
will(returnValue(request));
}});
byte[] b = createRequest(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
reader.addStructReader(Types.REQUEST, requestReader);
assertEquals(request, reader.readStruct(Types.REQUEST,
Request.class));
context.assertIsSatisfied();
}
@Test
public void testBitmapDecoding() throws Exception {
// Test sizes from 0 to 1000 bits
for(int i = 0; i < 1000; i++) {
// Create a BitSet of size i with one in ten bits set (on average)
BitSet requested = new BitSet(i);
for(int j = 0; j < i; j++) if(Math.random() < 0.1) requested.set(j);
// Encode the BitSet as a bitmap
int bytes = i % 8 == 0 ? i / 8 : i / 8 + 1;
byte[] bitmap = new byte[bytes];
for(int j = 0; j < i; j++) {
if(requested.get(j)) {
int offset = j / 8;
byte bit = (byte) (128 >> j % 8);
bitmap[offset] |= bit;
}
}
// Create a serialised request containing the bitmap
byte[] b = createRequest(bitmap);
// Deserialise the request
ByteArrayInputStream in = new ByteArrayInputStream(b);
Reader reader = readerFactory.createReader(in);
RequestReader requestReader = new RequestReader(packetFactory);
reader.addStructReader(Types.REQUEST, requestReader);
Request r = reader.readStruct(Types.REQUEST, Request.class);
BitSet decoded = r.getBitmap();
// Check that the decoded BitSet matches the original - we can't
// use equals() because of padding, but the first i bits should
// match and the cardinalities should be equal, indicating that no
// padding bits are set
for(int j = 0; j < i; j++) {
assertEquals(requested.get(j), decoded.get(j));
}
assertEquals(requested.cardinality(), decoded.cardinality());
}
}
private byte[] createRequest(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(Types.REQUEST);
// Allow one byte for the REQUEST tag, one byte for the padding length
// as a uint7, one byte for the BYTES tag, and five bytes for the
// length of the byte array as an int32
int size = MAX_PACKET_LENGTH - 8;
if(tooBig) size++;
assertTrue(size > Short.MAX_VALUE);
w.writeUint7((byte) 0);
w.writeBytes(new byte[size]);
assertEquals(tooBig, out.size() > MAX_PACKET_LENGTH);
return out.toByteArray();
}
private byte[] createRequest(byte[] bitmap) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer w = writerFactory.createWriter(out);
w.writeStructId(Types.REQUEST);
w.writeUint7((byte) 0);
w.writeBytes(bitmap);
return out.toByteArray();
}
}

View File

@@ -0,0 +1,244 @@
package net.sf.briar.protocol;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Signature;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.protocol.Author;
import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.Group;
import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.UnverifiedBatch;
import net.sf.briar.crypto.CryptoModule;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class UnverifiedBatchImplTest extends BriarTestCase {
// FIXME: This is an integration test, not a unit test
private final CryptoComponent crypto;
private final byte[] raw, raw1;
private final String subject;
private final long timestamp;
public UnverifiedBatchImplTest() {
super();
Injector i = Guice.createInjector(new CryptoModule());
crypto = i.getInstance(CryptoComponent.class);
Random r = new Random();
raw = new byte[123];
r.nextBytes(raw);
raw1 = new byte[1234];
r.nextBytes(raw1);
subject = "Unit tests are exciting";
timestamp = System.currentTimeMillis();
}
@Test
public void testIds() throws Exception {
// Calculate the expected batch and message IDs
MessageDigest messageDigest = crypto.getMessageDigest();
messageDigest.update(raw);
messageDigest.update(raw1);
BatchId batchId = new BatchId(messageDigest.digest());
messageDigest.update(raw);
MessageId messageId = new MessageId(messageDigest.digest());
messageDigest.update(raw1);
MessageId messageId1 = new MessageId(messageDigest.digest());
// Verify the batch
Mockery context = new Mockery();
final UnverifiedMessage message =
context.mock(UnverifiedMessage.class, "message");
final UnverifiedMessage message1 =
context.mock(UnverifiedMessage.class, "message1");
context.checking(new Expectations() {{
// First message
oneOf(message).getRaw();
will(returnValue(raw));
oneOf(message).getAuthor();
will(returnValue(null));
oneOf(message).getGroup();
will(returnValue(null));
oneOf(message).getParent();
will(returnValue(null));
oneOf(message).getSubject();
will(returnValue(subject));
oneOf(message).getTimestamp();
will(returnValue(timestamp));
oneOf(message).getBodyStart();
will(returnValue(10));
oneOf(message).getBodyLength();
will(returnValue(100));
// Second message
oneOf(message1).getRaw();
will(returnValue(raw1));
oneOf(message1).getAuthor();
will(returnValue(null));
oneOf(message1).getGroup();
will(returnValue(null));
oneOf(message1).getParent();
will(returnValue(null));
oneOf(message1).getSubject();
will(returnValue(subject));
oneOf(message1).getTimestamp();
will(returnValue(timestamp));
oneOf(message1).getBodyStart();
will(returnValue(10));
oneOf(message1).getBodyLength();
will(returnValue(1000));
}});
Collection<UnverifiedMessage> messages = Arrays.asList(message,
message1);
UnverifiedBatch batch = new UnverifiedBatchImpl(crypto, messages);
Batch verifiedBatch = batch.verify();
// Check that the batch and message IDs match
assertEquals(batchId, verifiedBatch.getId());
Collection<Message> verifiedMessages = verifiedBatch.getMessages();
assertEquals(2, verifiedMessages.size());
Iterator<Message> it = verifiedMessages.iterator();
Message verifiedMessage = it.next();
assertEquals(messageId, verifiedMessage.getId());
Message verifiedMessage1 = it.next();
assertEquals(messageId1, verifiedMessage1.getId());
context.assertIsSatisfied();
}
@Test
public void testSignatures() throws Exception {
final int signedByAuthor = 100, signedByGroup = 110;
final KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
final KeyPair groupKeyPair = crypto.generateSignatureKeyPair();
Signature signature = crypto.getSignature();
// Calculate the expected author and group signatures
signature.initSign(authorKeyPair.getPrivate());
signature.update(raw, 0, signedByAuthor);
final byte[] authorSignature = signature.sign();
signature.initSign(groupKeyPair.getPrivate());
signature.update(raw, 0, signedByGroup);
final byte[] groupSignature = signature.sign();
// Verify the batch
Mockery context = new Mockery();
final UnverifiedMessage message =
context.mock(UnverifiedMessage.class, "message");
final Author author = context.mock(Author.class);
final Group group = context.mock(Group.class);
final UnverifiedMessage message1 =
context.mock(UnverifiedMessage.class, "message1");
context.checking(new Expectations() {{
// First message
oneOf(message).getRaw();
will(returnValue(raw));
oneOf(message).getAuthor();
will(returnValue(author));
oneOf(author).getPublicKey();
will(returnValue(authorKeyPair.getPublic().getEncoded()));
oneOf(message).getLengthSignedByAuthor();
will(returnValue(signedByAuthor));
oneOf(message).getAuthorSignature();
will(returnValue(authorSignature));
oneOf(message).getGroup();
will(returnValue(group));
exactly(2).of(group).getPublicKey();
will(returnValue(groupKeyPair.getPublic().getEncoded()));
oneOf(message).getLengthSignedByGroup();
will(returnValue(signedByGroup));
oneOf(message).getGroupSignature();
will(returnValue(groupSignature));
oneOf(author).getId();
will(returnValue(new AuthorId(TestUtils.getRandomId())));
oneOf(group).getId();
will(returnValue(new GroupId(TestUtils.getRandomId())));
oneOf(message).getParent();
will(returnValue(null));
oneOf(message).getSubject();
will(returnValue(subject));
oneOf(message).getTimestamp();
will(returnValue(timestamp));
oneOf(message).getBodyStart();
will(returnValue(10));
oneOf(message).getBodyLength();
will(returnValue(100));
// Second message
oneOf(message1).getRaw();
will(returnValue(raw1));
oneOf(message1).getAuthor();
will(returnValue(null));
oneOf(message1).getGroup();
will(returnValue(null));
oneOf(message1).getParent();
will(returnValue(null));
oneOf(message1).getSubject();
will(returnValue(subject));
oneOf(message1).getTimestamp();
will(returnValue(timestamp));
oneOf(message1).getBodyStart();
will(returnValue(10));
oneOf(message1).getBodyLength();
will(returnValue(1000));
}});
Collection<UnverifiedMessage> messages = Arrays.asList(message,
message1);
UnverifiedBatch batch = new UnverifiedBatchImpl(crypto, messages);
batch.verify();
context.assertIsSatisfied();
}
@Test
public void testExceptionThrownIfMessageIsModified() throws Exception {
final int signedByAuthor = 100;
final KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
Signature signature = crypto.getSignature();
// Calculate the expected author signature
signature.initSign(authorKeyPair.getPrivate());
signature.update(raw, 0, signedByAuthor);
final byte[] authorSignature = signature.sign();
// Modify the message
raw[signedByAuthor / 2] ^= 0xff;
// Verify the batch
Mockery context = new Mockery();
final UnverifiedMessage message =
context.mock(UnverifiedMessage.class, "message");
final Author author = context.mock(Author.class);
final UnverifiedMessage message1 =
context.mock(UnverifiedMessage.class, "message1");
context.checking(new Expectations() {{
// First message - verification will fail at the author's signature
oneOf(message).getRaw();
will(returnValue(raw));
oneOf(message).getAuthor();
will(returnValue(author));
oneOf(author).getPublicKey();
will(returnValue(authorKeyPair.getPublic().getEncoded()));
oneOf(message).getLengthSignedByAuthor();
will(returnValue(signedByAuthor));
oneOf(message).getAuthorSignature();
will(returnValue(authorSignature));
}});
Collection<UnverifiedMessage> messages = Arrays.asList(message,
message1);
UnverifiedBatch batch = new UnverifiedBatchImpl(crypto, messages);
try {
batch.verify();
fail();
} catch(GeneralSecurityException expected) {}
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,177 @@
package net.sf.briar.protocol.simplex;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayOutputStream;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.BatchId;
import net.sf.briar.api.protocol.ProtocolWriterFactory;
import net.sf.briar.api.protocol.RawBatch;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.UniqueId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionRegistry;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.clock.ClockModule;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.protocol.ProtocolModule;
import net.sf.briar.protocol.duplex.DuplexProtocolModule;
import net.sf.briar.serial.SerialModule;
import net.sf.briar.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 connFactory;
private final ProtocolWriterFactory protoFactory;
private final ContactId contactId;
private final TransportId transportId;
private final byte[] secret;
public OutgoingSimplexConnectionTest() {
super();
context = new Mockery();
db = context.mock(DatabaseComponent.class);
Module testModule = new AbstractModule() {
@Override
public void configure() {
bind(DatabaseComponent.class).toInstance(db);
bind(Executor.class).annotatedWith(
DatabaseExecutor.class).toInstance(
Executors.newCachedThreadPool());
}
};
Injector i = Guice.createInjector(testModule, new ClockModule(),
new CryptoModule(), new SerialModule(), new TransportModule(),
new SimplexProtocolModule(), new ProtocolModule(),
new DuplexProtocolModule());
connRegistry = i.getInstance(ConnectionRegistry.class);
connFactory = i.getInstance(ConnectionWriterFactory.class);
protoFactory = i.getInstance(ProtocolWriterFactory.class);
contactId = new ContactId(234);
transportId = new TransportId(TestUtils.getRandomId());
secret = new byte[32];
}
@Test
public void testConnectionTooShort() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
out, MAX_PACKET_LENGTH, true);
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret, 0L, true);
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
connRegistry, connFactory, protoFactory, 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, true);
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret, 0L, true);
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
connRegistry, connFactory, protoFactory, ctx, transport);
context.checking(new Expectations() {{
// No transports to send
oneOf(db).generateTransportUpdate(contactId);
will(returnValue(null));
// No subscriptions to send
oneOf(db).generateSubscriptionUpdate(contactId);
will(returnValue(null));
// No acks to send
oneOf(db).generateAck(with(contactId), with(any(int.class)));
will(returnValue(null));
// No batches to send
oneOf(db).generateBatch(with(contactId), with(any(int.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, true);
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret, 0L, true);
OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
connRegistry, connFactory, protoFactory, ctx, transport);
final Ack ack = context.mock(Ack.class);
final BatchId batchId = new BatchId(TestUtils.getRandomId());
final RawBatch batch = context.mock(RawBatch.class);
final byte[] message = new byte[1234];
context.checking(new Expectations() {{
// No transports to send
oneOf(db).generateTransportUpdate(contactId);
will(returnValue(null));
// No subscriptions to send
oneOf(db).generateSubscriptionUpdate(contactId);
will(returnValue(null));
// One ack to send
oneOf(db).generateAck(with(contactId), with(any(int.class)));
will(returnValue(ack));
oneOf(ack).getBatchIds();
will(returnValue(Collections.singletonList(batchId)));
// No more acks
oneOf(db).generateAck(with(contactId), with(any(int.class)));
will(returnValue(null));
// One batch to send
oneOf(db).generateBatch(with(contactId), with(any(int.class)));
will(returnValue(batch));
oneOf(batch).getMessages();
will(returnValue(Collections.singletonList(message)));
// No more batches
oneOf(db).generateBatch(with(contactId), with(any(int.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 + message.length);
// The transport should have been disposed with exception == false
assertTrue(transport.getDisposed());
assertFalse(transport.getException());
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,223 @@
package net.sf.briar.protocol.simplex;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Random;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestDatabaseModule;
import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.KeyManager;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.MessagesAddedEvent;
import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageFactory;
import net.sf.briar.api.protocol.ProtocolReaderFactory;
import net.sf.briar.api.protocol.ProtocolWriterFactory;
import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportUpdate;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.ConnectionRecogniser;
import net.sf.briar.api.transport.ConnectionRegistry;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.api.transport.ContactTransport;
import net.sf.briar.clock.ClockModule;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.db.DatabaseModule;
import net.sf.briar.lifecycle.LifecycleModule;
import net.sf.briar.plugins.ImmediateExecutor;
import net.sf.briar.protocol.ProtocolModule;
import net.sf.briar.protocol.duplex.DuplexProtocolModule;
import net.sf.briar.serial.SerialModule;
import net.sf.briar.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 SimplexProtocolIntegrationTest extends BriarTestCase {
private static final long CLOCK_DIFFERENCE = 60 * 1000L;
private static final long LATENCY = 60 * 1000L;
private final File testDir = TestUtils.getTestDirectory();
private final File aliceDir = new File(testDir, "alice");
private final File bobDir = new File(testDir, "bob");
private final TransportId transportId;
private final byte[] initialSecret;
private final long epoch;
private Injector alice, bob;
public SimplexProtocolIntegrationTest() throws Exception {
super();
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 ClockModule(), new CryptoModule(),
new DatabaseModule(), new LifecycleModule(),
new ProtocolModule(), new SerialModule(),
new TestDatabaseModule(dir), new SimplexProtocolModule(),
new TransportModule(), new DuplexProtocolModule());
}
@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);
db.open(false);
// Start Alice's key manager
KeyManager km = alice.getInstance(KeyManager.class);
km.start();
// Add Bob as a contact
ContactId contactId = db.addContact();
ContactTransport ct = new ContactTransport(contactId, transportId,
epoch, CLOCK_DIFFERENCE, LATENCY, true);
db.addContactTransport(ct);
km.contactTransportAdded(ct, initialSecret.clone());
// Send Bob a message
String subject = "Hello";
byte[] body = "Hi Bob!".getBytes("UTF-8");
MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
Message message = messageFactory.createMessage(null, subject, body);
db.addLocalPrivateMessage(message, contactId);
// Create an outgoing simplex connection
ByteArrayOutputStream out = new ByteArrayOutputStream();
ConnectionRegistry connRegistry =
alice.getInstance(ConnectionRegistry.class);
ConnectionWriterFactory connFactory =
alice.getInstance(ConnectionWriterFactory.class);
ProtocolWriterFactory protoFactory =
alice.getInstance(ProtocolWriterFactory.class);
TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
out, Long.MAX_VALUE, false);
ConnectionContext ctx = km.getConnectionContext(contactId, transportId);
assertNotNull(ctx);
OutgoingSimplexConnection simplex = new OutgoingSimplexConnection(db,
connRegistry, connFactory, protoFactory, 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);
db.open(false);
// Start Bob's key manager
KeyManager km = bob.getInstance(KeyManager.class);
km.start();
// Add Alice as a contact
ContactId contactId = db.addContact();
ContactTransport ct = new ContactTransport(contactId, transportId,
epoch, CLOCK_DIFFERENCE, LATENCY, false);
db.addContactTransport(ct);
km.contactTransportAdded(ct, initialSecret.clone());
// Set up a database listener
MessageListener listener = new MessageListener();
db.addListener(listener);
// Fake a transport update from Alice
TransportUpdate transportUpdate = new TransportUpdate() {
public Collection<Transport> getTransports() {
Transport t = new Transport(transportId);
return Collections.singletonList(t);
}
public long getTimestamp() {
return System.currentTimeMillis();
}
};
db.receiveTransportUpdate(contactId, transportUpdate);
// 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
ConnectionRegistry connRegistry =
bob.getInstance(ConnectionRegistry.class);
ConnectionReaderFactory connFactory =
bob.getInstance(ConnectionReaderFactory.class);
ProtocolReaderFactory protoFactory =
bob.getInstance(ProtocolReaderFactory.class);
TestSimplexTransportReader transport =
new TestSimplexTransportReader(in);
IncomingSimplexConnection simplex = new IncomingSimplexConnection(
new ImmediateExecutor(), new ImmediateExecutor(), db,
connRegistry, connFactory, protoFactory, ctx, transport);
// No messages should have been added yet
assertFalse(listener.messagesAdded);
// 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.messagesAdded);
// Clean up
km.stop();
db.close();
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
private static class MessageListener implements DatabaseListener {
private boolean messagesAdded = false;
public void eventOccurred(DatabaseEvent e) {
if(e instanceof MessagesAddedEvent)
messagesAdded = true;
}
}
}

View File

@@ -0,0 +1,39 @@
package net.sf.briar.protocol.simplex;
import java.io.InputStream;
import net.sf.briar.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 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;
}
}

View File

@@ -0,0 +1,48 @@
package net.sf.briar.protocol.simplex;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
class TestSimplexTransportWriter implements SimplexTransportWriter {
private final ByteArrayOutputStream out;
private final long capacity;
private final boolean flush;
private boolean disposed = false, exception = false;
TestSimplexTransportWriter(ByteArrayOutputStream out, long capacity,
boolean flush) {
this.out = out;
this.capacity = capacity;
this.flush = flush;
}
public long getCapacity() {
return capacity;
}
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;
}
}

View File

@@ -0,0 +1,556 @@
package net.sf.briar.serial;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.Bytes;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.serial.Consumer;
import net.sf.briar.api.serial.StructReader;
import net.sf.briar.api.serial.Reader;
import net.sf.briar.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("FFFE");
assertFalse(r.readBoolean());
assertTrue(r.readBoolean());
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 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 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 testReadInt64() throws Exception {
setContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF"
+ "FA7FFFFFFFFFFFFFFF" + "FA8000000000000000");
assertEquals(0L, r.readInt64());
assertEquals(-1L, r.readInt64());
assertEquals(Long.MAX_VALUE, r.readInt64());
assertEquals(Long.MIN_VALUE, r.readInt64());
assertTrue(r.eof());
}
@Test
public void testReadIntAny() throws Exception {
setContents("00" + "7F" + "FD80" + "FDFF" + "FC0080" + "FC7FFF"
+ "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000");
assertEquals(0L, r.readIntAny());
assertEquals(127L, r.readIntAny());
assertEquals(-128L, r.readIntAny());
assertEquals(-1L, r.readIntAny());
assertEquals(128L, r.readIntAny());
assertEquals(32767L, r.readIntAny());
assertEquals(32768L, r.readIntAny());
assertEquals(2147483647L, r.readIntAny());
assertEquals(2147483648L, r.readIntAny());
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 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 testReadString() throws Exception {
setContents("F703666F6F" + "83666F6F" + "F700" + "80");
assertEquals("foo", r.readString());
assertEquals("foo", r.readString());
assertEquals("", r.readString());
assertEquals("", r.readString());
assertTrue(r.eof());
}
@Test
public void testReadStringMaxLength() throws Exception {
setContents("83666F6F" + "83666F6F");
assertEquals("foo", r.readString(3));
try {
r.readString(2);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadBytes() throws Exception {
setContents("F603010203" + "93010203" + "F600" + "90");
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes());
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes());
assertArrayEquals(new byte[] {}, r.readBytes());
assertArrayEquals(new byte[] {}, r.readBytes());
assertTrue(r.eof());
}
@Test
public void testReadBytesMaxLength() throws Exception {
setContents("93010203" + "93010203");
assertArrayEquals(new byte[] {1, 2, 3}, r.readBytes(3));
try {
r.readBytes(2);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadShortList() throws Exception {
setContents("A" + "3" + "01" + "83666F6F" + "FC0080");
List<Object> l = r.readList(Object.class);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals((byte) 1, l.get(0));
assertEquals("foo", l.get(1));
assertEquals((short) 128, l.get(2));
assertTrue(r.eof());
}
@Test
public void testReadList() throws Exception {
setContents("F5" + "01" + "83666F6F" + "FC0080" + "F3");
List<Object> l = r.readList(Object.class);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals((byte) 1, l.get(0));
assertEquals("foo", l.get(1));
assertEquals((short) 128, l.get(2));
assertTrue(r.eof());
}
@Test
public void testReadListTypeSafe() throws Exception {
setContents("A" + "3" + "01" + "02" + "03");
List<Byte> l = r.readList(Byte.class);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals(Byte.valueOf((byte) 1), l.get(0));
assertEquals(Byte.valueOf((byte) 2), l.get(1));
assertEquals(Byte.valueOf((byte) 3), l.get(2));
assertTrue(r.eof());
}
@Test
public void testReadListTypeSafeThrowsFormatException() throws Exception {
setContents("A" + "3" + "01" + "83666F6F" + "03");
// Trying to read a mixed list as a list of bytes should throw a
// FormatException
try {
r.readList(Byte.class);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadShortMap() throws Exception {
setContents("B" + "2" + "83666F6F" + "7B" + "90" + "F2");
Map<Object, Object> m = r.readMap(Object.class, Object.class);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals((byte) 123, m.get("foo"));
Bytes b = new Bytes(new byte[] {});
assertTrue(m.containsKey(b));
assertNull(m.get(b));
assertTrue(r.eof());
}
@Test
public void testReadMap() throws Exception {
setContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3");
Map<Object, Object> m = r.readMap(Object.class, Object.class);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals((byte) 123, m.get("foo"));
Bytes b = new Bytes(new byte[] {});
assertTrue(m.containsKey(b));
assertNull(m.get(b));
assertTrue(r.eof());
}
@Test
public void testReadMapTypeSafe() throws Exception {
setContents("B" + "2" + "83666F6F" + "7B" + "80" + "F2");
Map<String, Byte> m = r.readMap(String.class, Byte.class);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals(Byte.valueOf((byte) 123), m.get("foo"));
assertTrue(m.containsKey(""));
assertNull(m.get(""));
assertTrue(r.eof());
}
@Test
public void testMapKeysMustBeUnique() throws Exception {
setContents("B" + "2" + "83666F6F" + "01" + "83626172" + "02"
+ "B" + "2" + "83666F6F" + "01" + "83666F6F" + "02");
// The first map has unique keys
Map<String, Byte> m = r.readMap(String.class, Byte.class);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals(Byte.valueOf((byte) 1), m.get("foo"));
assertEquals(Byte.valueOf((byte) 2), m.get("bar"));
// The second map has a duplicate key
try {
r.readMap(String.class, Byte.class);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadDelimitedList() throws Exception {
setContents("F5" + "01" + "83666F6F" + "FC0080" + "F3");
List<Object> l = r.readList(Object.class);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals((byte) 1, l.get(0));
assertEquals("foo", l.get(1));
assertEquals((short) 128, l.get(2));
assertTrue(r.eof());
}
@Test
public void testReadDelimitedListElements() throws Exception {
setContents("F5" + "01" + "83666F6F" + "FC0080" + "F3");
assertTrue(r.hasListStart());
r.readListStart();
assertFalse(r.hasListEnd());
assertEquals((byte) 1, r.readIntAny());
assertFalse(r.hasListEnd());
assertEquals("foo", r.readString());
assertFalse(r.hasListEnd());
assertEquals((short) 128, r.readIntAny());
assertTrue(r.hasListEnd());
r.readListEnd();
assertTrue(r.eof());
}
@Test
public void testReadDelimitedListTypeSafe() throws Exception {
setContents("F5" + "01" + "02" + "03" + "F3");
List<Byte> l = r.readList(Byte.class);
assertNotNull(l);
assertEquals(3, l.size());
assertEquals(Byte.valueOf((byte) 1), l.get(0));
assertEquals(Byte.valueOf((byte) 2), l.get(1));
assertEquals(Byte.valueOf((byte) 3), l.get(2));
assertTrue(r.eof());
}
@Test
public void testReadDelimitedMap() throws Exception {
setContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3");
Map<Object, Object> m = r.readMap(Object.class, Object.class);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals((byte) 123, m.get("foo"));
Bytes b = new Bytes(new byte[] {});
assertTrue(m.containsKey(b));
assertNull(m.get(b));
assertTrue(r.eof());
}
@Test
public void testReadDelimitedMapEntries() throws Exception {
setContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3");
assertTrue(r.hasMapStart());
r.readMapStart();
assertFalse(r.hasMapEnd());
assertEquals("foo", r.readString());
assertFalse(r.hasMapEnd());
assertEquals((byte) 123, r.readIntAny());
assertFalse(r.hasMapEnd());
assertArrayEquals(new byte[] {}, r.readBytes());
assertFalse(r.hasMapEnd());
assertTrue(r.hasNull());
r.readNull();
assertTrue(r.hasMapEnd());
r.readMapEnd();
assertTrue(r.eof());
}
@Test
public void testReadDelimitedMapTypeSafe() throws Exception {
setContents("F4" + "83666F6F" + "7B" + "80" + "F2" + "F3");
Map<String, Byte> m = r.readMap(String.class, Byte.class);
assertNotNull(m);
assertEquals(2, m.size());
assertEquals(Byte.valueOf((byte) 123), m.get("foo"));
assertTrue(m.containsKey(""));
assertNull(m.get(""));
assertTrue(r.eof());
}
@Test
@SuppressWarnings("unchecked")
public void testReadNestedMapsAndLists() throws Exception {
setContents("B" + "1" + "B" + "1" + "83666F6F" + "7B"
+ "A" + "1" + "01");
Map<Object, Object> m = r.readMap(Object.class, Object.class);
assertNotNull(m);
assertEquals(1, m.size());
Entry<Object, Object> e = m.entrySet().iterator().next();
Map<Object, Object> m1 = (Map<Object, Object>) e.getKey();
assertNotNull(m1);
assertEquals(1, m1.size());
assertEquals((byte) 123, m1.get("foo"));
List<Object> l = (List<Object>) e.getValue();
assertNotNull(l);
assertEquals(1, l.size());
assertEquals((byte) 1, l.get(0));
assertTrue(r.eof());
}
@Test
public void testReadStruct() throws Exception {
setContents("C0" + "83666F6F" + "F1" + "FF" + "83666F6F");
// Add readers for two structs
r.addStructReader(0, new StructReader<Foo>() {
public Foo readStruct(Reader r) throws IOException {
r.readStructId(0);
return new Foo(r.readString());
}
});
r.addStructReader(255, new StructReader<Bar>() {
public Bar readStruct(Reader r) throws IOException {
r.readStructId(255);
return new Bar(r.readString());
}
});
// Test both ID formats, short and long
assertTrue(r.hasStruct(0));
assertEquals("foo", r.readStruct(0, Foo.class).s);
assertTrue(r.hasStruct(255));
assertEquals("foo", r.readStruct(255, Bar.class).s);
}
@Test
public void testReadStructWithConsumer() throws Exception {
setContents("C0" + "83666F6F" + "F1" + "FF" + "83666F6F");
// Add readers for two structs
r.addStructReader(0, new StructReader<Foo>() {
public Foo readStruct(Reader r) throws IOException {
r.readStructId(0);
return new Foo(r.readString());
}
});
r.addStructReader(255, new StructReader<Bar>() {
public Bar readStruct(Reader r) throws IOException {
r.readStructId(255);
return new Bar(r.readString());
}
});
// Add a consumer
final ByteArrayOutputStream out = new ByteArrayOutputStream();
r.addConsumer(new Consumer() {
public void write(byte b) throws IOException {
out.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
});
// Test both ID formats, short and long
assertTrue(r.hasStruct(0));
assertEquals("foo", r.readStruct(0, Foo.class).s);
assertTrue(r.hasStruct(255));
assertEquals("foo", r.readStruct(255, Bar.class).s);
// Check that everything was passed to the consumer
assertEquals("C0" + "83666F6F" + "F1" + "FF" + "83666F6F",
StringUtils.toHexString(out.toByteArray()));
}
@Test
public void testUnknownStructIdThrowsFormatException() throws Exception {
setContents("C0" + "83666F6F");
assertTrue(r.hasStruct(0));
// No reader has been added for struct ID 0
try {
r.readStruct(0, Foo.class);
fail();
} catch(FormatException expected) {}
}
@Test
public void testWrongClassThrowsFormatException() throws Exception {
setContents("C0" + "83666F6F");
// Add a reader for struct ID 0, class Foo
r.addStructReader(0, new StructReader<Foo>() {
public Foo readStruct(Reader r) throws IOException {
r.readStructId(0);
return new Foo(r.readString());
}
});
assertTrue(r.hasStruct(0));
// Trying to read the struct as class Bar should throw a FormatException
try {
r.readStruct(0, Bar.class);
fail();
} catch(FormatException expected) {}
}
@Test
public void testReadListUsingStructReader() throws Exception {
setContents("A" + "1" + "C0" + "83666F6F");
// Add a reader for a struct
r.addStructReader(0, new StructReader<Foo>() {
public Foo readStruct(Reader r) throws IOException {
r.readStructId(0);
return new Foo(r.readString());
}
});
// Check that the reader is used for lists
List<Foo> l = r.readList(Foo.class);
assertEquals(1, l.size());
assertEquals("foo", l.get(0).s);
}
@Test
public void testReadMapUsingStructReader() throws Exception {
setContents("B" + "1" + "C0" + "83666F6F" + "C1" + "83626172");
// Add readers for two structs
r.addStructReader(0, new StructReader<Foo>() {
public Foo readStruct(Reader r) throws IOException {
r.readStructId(0);
return new Foo(r.readString());
}
});
r.addStructReader(1, new StructReader<Bar>() {
public Bar readStruct(Reader r) throws IOException {
r.readStructId(1);
return new Bar(r.readString());
}
});
// Check that the readers are used for maps
Map<Foo, Bar> m = r.readMap(Foo.class, Bar.class);
assertEquals(1, m.size());
Entry<Foo, Bar> e = m.entrySet().iterator().next();
assertEquals("foo", e.getKey().s);
assertEquals("bar", e.getValue().s);
}
@Test
public void testMaxLengthAppliesInsideMap() throws Exception {
setContents("B" + "1" + "83666F6F" + "93010203");
r.setMaxStringLength(3);
r.setMaxBytesLength(3);
Map<String, Bytes> m = r.readMap(String.class, Bytes.class);
String key = "foo";
Bytes value = new Bytes(new byte[] {1, 2, 3});
assertEquals(Collections.singletonMap(key, value), m);
// The max string length should be applied inside the map
setContents("B" + "1" + "83666F6F" + "93010203");
r.setMaxStringLength(2);
try {
r.readMap(String.class, Bytes.class);
fail();
} catch(FormatException expected) {}
// The max bytes length should be applied inside the map
setContents("B" + "1" + "83666F6F" + "93010203");
r.setMaxBytesLength(2);
try {
r.readMap(String.class, Bytes.class);
fail();
} catch(FormatException expected) {}
}
@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);
}
private static class Foo {
private final String s;
private Foo(String s) {
this.s = s;
}
}
private static class Bar {
private final String s;
private Bar(String s) {
this.s = s;
}
}
}

View File

@@ -0,0 +1,291 @@
package net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.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(0L);
w.writeInt64(-1L);
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 testWriteShortString() throws IOException {
w.writeString("foo bar baz bam");
// SHORT_STRING tag, length 15, UTF-8 bytes
checkContents("8" + "F" + "666F6F206261722062617A2062616D");
}
@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 testWriteShortBytes() throws IOException {
w.writeBytes(new byte[] {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
});
// SHORT_BYTES tag, length 15, bytes
checkContents("9" + "F" + "000102030405060708090A0B0C0D0E");
}
@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 testWriteShortList() throws IOException {
List<Object> l = new ArrayList<Object>();
for(int i = 0; i < 15; i++) l.add(i);
w.writeList(l);
// SHORT_LIST tag, length, elements as uint7
checkContents("A" + "F" + "000102030405060708090A0B0C0D0E");
}
@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" + "F3");
}
@Test
public void testListCanContainNull() throws IOException {
List<Object> l = new ArrayList<Object>();
l.add(1);
l.add(null);
l.add(2);
w.writeList(l);
// SHORT_LIST tag, length, 1 as uint7, null, 2 as uint7
checkContents("A" + "3" + "01" + "F2" + "02");
}
@Test
public void testWriteShortMap() throws IOException {
// Use LinkedHashMap to get predictable iteration order
Map<Object, Object> m = new LinkedHashMap<Object, Object>();
for(int i = 0; i < 15; i++) m.put(i, i + 1);
w.writeMap(m);
// SHORT_MAP tag, size, entries as uint7
checkContents("B" + "F" + "0001" + "0102" + "0203" + "0304" + "0405"
+ "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C"
+ "0C0D" + "0D0E" + "0E0F");
}
@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" + "F3");
}
@Test
public void testWriteDelimitedList() throws IOException {
w.writeListStart();
w.writeIntAny((byte) 1); // Written as uint7
w.writeString("foo"); // Written as short string
w.writeIntAny(128L); // Written as an int16
w.writeListEnd();
// LIST tag, 1 as uint7, "foo" as short string, 128 as int16,
// END tag
checkContents("F5" + "01" + "83666F6F" + "FC0080" + "F3");
}
@Test
public void testWriteDelimitedMap() throws IOException {
w.writeMapStart();
w.writeString("foo"); // Written as short string
w.writeIntAny(123); // Written as a uint7
w.writeBytes(new byte[] {}); // Written as short bytes
w.writeNull();
w.writeMapEnd();
// MAP tag, "foo" as short string, 123 as uint7,
// byte[] {} as short bytes, NULL tag, END tag
checkContents("F4" + "83666F6F" + "7B" + "90" + "F2" + "F3");
}
@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);
// SHORT_MAP tag, length 1, SHORT_MAP tag, length 1,
// "foo" as short string, 123 as uint7, SHORT_LIST tag, length 1,
// 1 as uint7
checkContents("B" + "1" + "B" + "1" + "83666F6F" + "7B" + "A1" + "01");
}
@Test
public void testWriteNull() throws IOException {
w.writeNull();
checkContents("F2");
}
@Test
public void testWriteShortStructId() throws IOException {
w.writeStructId(0);
w.writeStructId(31);
// SHORT_STRUCT tag (3 bits), 0 (5 bits), SHORT_STRUCT tag (3 bits),
// 31 (5 bits)
checkContents("C0" + "DF");
}
@Test
public void testWriteStructId() throws IOException {
w.writeStructId(32);
w.writeStructId(255);
// STRUCT tag, 32 as uint8, STRUCT tag, 255 as uint8
checkContents("F1" + "20" + "F1" + "FF");
}
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()));
}
}

View File

@@ -0,0 +1,107 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import net.sf.briar.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();
}
@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();
}
@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();
}
@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();
}
}

View File

@@ -0,0 +1,73 @@
package net.sf.briar.transport;
import java.util.Arrays;
import java.util.Collections;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionRegistry;
import org.junit.Test;
public class ConnectionRegistryImplTest extends BriarTestCase {
private final ContactId contactId, contactId1;
private final TransportId transportId, transportId1;
public ConnectionRegistryImplTest() {
super();
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(Collections.singletonList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Register an identical connection - lookup should be unaffected
c.registerConnection(contactId, transportId);
assertEquals(Collections.singletonList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Unregister one of the connections - lookup should be unaffected
c.unregisterConnection(contactId, transportId);
assertEquals(Collections.singletonList(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);
assertEquals(Arrays.asList(contactId, contactId1),
c.getConnectedContacts(transportId));
assertEquals(Collections.singletonList(contactId1),
c.getConnectedContacts(transportId1));
}
}

View File

@@ -0,0 +1,157 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.CONNECTION_WINDOW_SIZE;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import static org.junit.Assert.assertArrayEquals;
import java.util.Collection;
import net.sf.briar.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));
}
}
}
}

View File

@@ -0,0 +1,124 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import net.sf.briar.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();
}
}

View File

@@ -0,0 +1,183 @@
package net.sf.briar.transport;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import java.io.ByteArrayInputStream;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.crypto.AuthenticatedCipher;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.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 ErasableKey frameKey;
public IncomingEncryptionLayerTest() {
super();
Injector i = Guice.createInjector(new CryptoModule());
crypto = i.getInstance(CryptoComponent.class);
frameCipher = crypto.getFrameCipher();
frameKey = crypto.generateTestKey();
}
@Test
public void testReadValidFrames() throws Exception {
// Generate two valid frames
byte[] frame = generateFrame(0L, FRAME_LENGTH, 123, false, false);
byte[] frame1 = generateFrame(1L, 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(0L, 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(0L, 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(0L, 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(0L, 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(0L, 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(0L, 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(0L, FRAME_LENGTH, MAX_PAYLOAD_LENGTH, true,
false);
byte[] frame1 = generateFrame(1L, 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;
}
}

View File

@@ -0,0 +1,159 @@
package net.sf.briar.transport;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static net.sf.briar.api.transport.TransportConstants.AAD_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayOutputStream;
import net.sf.briar.BriarTestCase;
import net.sf.briar.api.crypto.AuthenticatedCipher;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.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() {
super();
Injector i = Guice.createInjector(new CryptoModule());
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];
ErasableKey frameKey = crypto.generateTestKey();
// 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.generateTestKey(),
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.generateTestKey(),
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.generateTestKey(),
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.generateTestKey(),
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.generateTestKey(),
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());
}
}

View File

@@ -0,0 +1,141 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.NullCipher;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.TemporarySecret;
import net.sf.briar.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 Cipher tagCipher = new NullCipher();
final byte[] secret = new byte[32];
new Random().nextBytes(secret);
final boolean alice = false;
final ErasableKey tagKey = context.mock(ErasableKey.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Add secret
oneOf(crypto).getTagCipher();
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
exactly(16).of(crypto).encodeTag(with(any(byte[].class)),
with(tagCipher), with(tagKey), with(any(long.class)));
will(new EncodeTagAction());
oneOf(tagKey).erase();
// Remove secret
oneOf(crypto).getTagCipher();
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
exactly(16).of(crypto).encodeTag(with(any(byte[].class)),
with(tagCipher), with(tagKey), with(any(long.class)));
will(new EncodeTagAction());
oneOf(tagKey).erase();
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 0L,
0L, 0L, alice, 0L, secret, 0L, 0L, new byte[4]);
TransportConnectionRecogniser recogniser =
new TransportConnectionRecogniser(crypto, db, transportId);
recogniser.addSecret(s);
recogniser.removeSecret(contactId, 0L);
// The secret should have been erased
assertArrayEquals(new byte[32], secret);
context.assertIsSatisfied();
}
@Test
public void testAcceptConnection() throws Exception {
Mockery context = new Mockery();
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Cipher tagCipher = new NullCipher();
final byte[] secret = new byte[32];
new Random().nextBytes(secret);
final boolean alice = false;
final ErasableKey tagKey = context.mock(ErasableKey.class);
final DatabaseComponent db = context.mock(DatabaseComponent.class);
context.checking(new Expectations() {{
// Add secret
oneOf(crypto).getTagCipher();
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
exactly(16).of(crypto).encodeTag(with(any(byte[].class)),
with(tagCipher), with(tagKey), with(any(long.class)));
will(new EncodeTagAction());
oneOf(tagKey).erase();
// Accept connection
oneOf(crypto).getTagCipher();
will(returnValue(tagCipher));
oneOf(crypto).deriveTagKey(secret, !alice);
will(returnValue(tagKey));
// The window should slide to include connection 16
oneOf(crypto).encodeTag(with(any(byte[].class)), with(tagCipher),
with(tagKey), with(16L));
will(new EncodeTagAction());
// The updated window should be stored
oneOf(db).setConnectionWindow(contactId, transportId, 0L, 1L,
new byte[] {0, 1, 0, 0});
oneOf(tagKey).erase();
// Accept connection again - no expectations
}});
TemporarySecret s = new TemporarySecret(contactId, transportId, 0L,
0L, 0L, alice, 0L, secret, 0L, 0L, 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(0L, 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(3);
ByteUtils.writeUint32(connection, tag, 0);
return null;
}
}
}

View File

@@ -0,0 +1,173 @@
package net.sf.briar.transport;
import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
import static net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.crypto.AuthenticatedCipher;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.crypto.CryptoModule;
import net.sf.briar.transport.ConnectionReaderImpl;
import net.sf.briar.transport.ConnectionWriterFactoryImpl;
import net.sf.briar.transport.ConnectionWriterImpl;
import net.sf.briar.transport.IncomingEncryptionLayer;
import net.sf.briar.transport.OutgoingEncryptionLayer;
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 ErasableKey frameKey;
public TransportIntegrationTest() {
super();
Module testModule = new AbstractModule() {
@Override
public void configure() {
bind(ConnectionWriterFactory.class).to(
ConnectionWriterFactoryImpl.class);
}
};
Injector i = Guice.createInjector(testModule, new CryptoModule());
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, 0L, 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
ErasableKey frameCopy = frameKey.copy();
// Write the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
FrameWriter encryptionOut = new OutgoingEncryptionLayer(out,
Long.MAX_VALUE, frameCipher, frameCopy, FRAME_LENGTH);
ConnectionWriter 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);
ConnectionReader 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);
}
@Test
public void testOverheadWithTag() throws Exception {
ByteArrayOutputStream out =
new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
ConnectionContext ctx = new ConnectionContext(contactId, transportId,
secret, 0L, true);
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
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, 0L, true);
ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
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);
}
}

View File

@@ -0,0 +1,66 @@
package net.sf.briar.util;
import net.sf.briar.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(0L, ByteUtils.readUint32(b, 1));
b = StringUtils.fromHexString("0000000001");
assertEquals(1L, 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(0L, b, 1);
assertEquals("0000000000", StringUtils.toHexString(b));
ByteUtils.writeUint32(1L, 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));
}
}
}

View File

@@ -0,0 +1,165 @@
package net.sf.briar.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
import net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.util.FileUtils.Callback;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class FileUtilsTest extends BriarTestCase {
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testCreateTempFile() throws IOException {
File temp = FileUtils.createTempFile();
assertTrue(temp.exists());
assertTrue(temp.isFile());
assertEquals(0L, temp.length());
temp.delete();
}
@Test
public void testCopy() throws IOException {
File src = new File(testDir, "src");
File dest = new File(testDir, "dest");
TestUtils.createFile(src, "Foo bar\r\nBar foo\r\n");
long length = src.length();
FileUtils.copy(src, dest);
assertEquals(length, dest.length());
Scanner in = new Scanner(dest);
assertTrue(in.hasNextLine());
assertEquals("Foo bar", in.nextLine());
assertTrue(in.hasNextLine());
assertEquals("Bar foo", in.nextLine());
assertFalse(in.hasNext());
in.close();
}
@Test
public void testCopyFromStream() throws IOException {
File src = new File(testDir, "src");
File dest = new File(testDir, "dest");
TestUtils.createFile(src, "Foo bar\r\nBar foo\r\n");
long length = src.length();
InputStream is = new FileInputStream(src);
is.skip(4);
FileUtils.copy(is, dest);
assertEquals(length - 4, dest.length());
Scanner in = new Scanner(dest);
assertTrue(in.hasNextLine());
assertEquals("bar", in.nextLine());
assertTrue(in.hasNextLine());
assertEquals("Bar foo", in.nextLine());
assertFalse(in.hasNext());
in.close();
}
@Test
public void testCopyRecursively() throws IOException {
final File dest1 = new File(testDir, "dest/abc/def/1");
final File dest2 = new File(testDir, "dest/abc/def/2");
final File dest3 = new File(testDir, "dest/abc/3");
Mockery context = new Mockery();
final Callback callback = context.mock(Callback.class);
context.checking(new Expectations() {{
oneOf(callback).processingFile(dest1);
oneOf(callback).processingFile(dest2);
oneOf(callback).processingFile(dest3);
}});
copyRecursively(callback);
context.assertIsSatisfied();
}
@Test
public void testCopyRecursivelyNoCallback() throws IOException {
copyRecursively(null);
}
private void copyRecursively(Callback callback) throws IOException {
TestUtils.createFile(new File(testDir, "abc/def/1"), "one one one");
TestUtils.createFile(new File(testDir, "abc/def/2"), "two two two");
TestUtils.createFile(new File(testDir, "abc/3"), "three three three");
File dest = new File(testDir, "dest");
dest.mkdir();
FileUtils.copyRecursively(new File(testDir, "abc"), dest, callback);
File dest1 = new File(testDir, "dest/abc/def/1");
assertTrue(dest1.exists());
assertTrue(dest1.isFile());
assertEquals("one one one".length(), dest1.length());
File dest2 = new File(testDir, "dest/abc/def/2");
assertTrue(dest2.exists());
assertTrue(dest2.isFile());
assertEquals("two two two".length(), dest2.length());
File dest3 = new File(testDir, "dest/abc/3");
assertTrue(dest3.exists());
assertTrue(dest3.isFile());
assertEquals("three three three".length(), dest3.length());
}
@Test
public void testDeleteFile() throws IOException {
File foo = new File(testDir, "foo");
foo.createNewFile();
assertTrue(foo.exists());
FileUtils.delete(foo);
assertFalse(foo.exists());
}
@Test
public void testDeleteDirectory() throws IOException {
File f1 = new File(testDir, "abc/def/1");
File f2 = new File(testDir, "abc/def/2");
File f3 = new File(testDir, "abc/3");
File abc = new File(testDir, "abc");
File def = new File(testDir, "abc/def");
TestUtils.createFile(f1, "one one one");
TestUtils.createFile(f2, "two two two");
TestUtils.createFile(f3, "three three three");
assertTrue(f1.exists());
assertTrue(f2.exists());
assertTrue(f3.exists());
assertTrue(abc.exists());
assertTrue(def.exists());
FileUtils.delete(def);
assertFalse(f1.exists());
assertFalse(f2.exists());
assertTrue(f3.exists());
assertTrue(abc.exists());
assertFalse(def.exists());
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -0,0 +1,44 @@
package net.sf.briar.util;
import static org.junit.Assert.assertArrayEquals;
import net.sf.briar.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);
}
}

View File

@@ -0,0 +1,202 @@
package net.sf.briar.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 net.sf.briar.BriarTestCase;
import net.sf.briar.TestUtils;
import net.sf.briar.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);
}
}