Move all unit tests to their modules and remove briar-tests

This commit is contained in:
Torsten Grote
2016-12-12 17:06:30 -02:00
parent 32be148c7a
commit 1081a08ea9
130 changed files with 441 additions and 433 deletions

View File

@@ -0,0 +1,20 @@
package org.briarproject.bramble;
import org.hamcrest.Description;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
public class RunAction implements Action {
@Override
public Object invoke(Invocation invocation) throws Throwable {
Runnable task = (Runnable) invocation.getParameter(0);
task.run();
return null;
}
@Override
public void describeTo(Description description) {
description.appendText("runs a runnable");
}
}

View File

@@ -0,0 +1,169 @@
package org.briarproject.bramble.client;
import org.briarproject.bramble.ValidatorTestCase;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
public class BdfMessageValidatorTest extends ValidatorTestCase {
@NotNullByDefault
private final BdfMessageValidator failIfSubclassIsCalled =
new BdfMessageValidator(clientHelper, metadataEncoder, clock) {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body)
throws InvalidMessageException, FormatException {
throw new AssertionError();
}
};
private final BdfList body = BdfList.of(123, 456);
private final BdfDictionary dictionary = new BdfDictionary();
private final Metadata meta = new Metadata();
public BdfMessageValidatorTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
}
@Test(expected = InvalidMessageException.class)
public void testRejectsFarFutureTimestamp() throws Exception {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp - MAX_CLOCK_DIFFERENCE - 1));
}});
failIfSubclassIsCalled.validateMessage(message, group);
}
@Test
public void testAcceptsMaxTimestamp() throws Exception {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp - MAX_CLOCK_DIFFERENCE));
oneOf(clientHelper).toList(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
will(returnValue(body));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(meta));
}});
@NotNullByDefault
BdfMessageValidator v = new BdfMessageValidator(clientHelper,
metadataEncoder, clock) {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList b) throws InvalidMessageException, FormatException {
assertSame(message, m);
assertSame(group, g);
assertSame(body, b);
return new BdfMessageContext(dictionary);
}
};
MessageContext messageContext = v.validateMessage(message, group);
assertEquals(0, messageContext.getDependencies().size());
assertSame(meta, messageContext.getMetadata());
}
@Test(expected = InvalidMessageException.class)
public void testRejectsTooShortMessage() throws Exception {
final byte[] invalidRaw = new byte[MESSAGE_HEADER_LENGTH];
// Use a mock message so the length of the raw message can be invalid
final Message invalidMessage = context.mock(Message.class);
context.checking(new Expectations() {{
oneOf(invalidMessage).getTimestamp();
will(returnValue(timestamp));
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(invalidMessage).getRaw();
will(returnValue(invalidRaw));
}});
failIfSubclassIsCalled.validateMessage(invalidMessage, group);
}
@Test
public void testAcceptsMinLengthMessage() throws Exception {
final byte[] shortRaw = new byte[MESSAGE_HEADER_LENGTH + 1];
final Message shortMessage =
new Message(messageId, groupId, timestamp, shortRaw);
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(clientHelper).toList(shortRaw, MESSAGE_HEADER_LENGTH,
shortRaw.length - MESSAGE_HEADER_LENGTH);
will(returnValue(body));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(meta));
}});
@NotNullByDefault
BdfMessageValidator v = new BdfMessageValidator(clientHelper,
metadataEncoder, clock) {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList b) throws InvalidMessageException, FormatException {
assertSame(shortMessage, m);
assertSame(group, g);
assertSame(body, b);
return new BdfMessageContext(dictionary);
}
};
MessageContext messageContext = v.validateMessage(shortMessage, group);
assertEquals(0, messageContext.getDependencies().size());
assertSame(meta, messageContext.getMetadata());
}
@Test(expected = InvalidMessageException.class)
public void testRejectsInvalidBdfList() throws Exception {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(clientHelper).toList(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
will(throwException(new FormatException()));
}});
failIfSubclassIsCalled.validateMessage(message, group);
}
@Test(expected = InvalidMessageException.class)
public void testRethrowsFormatExceptionFromSubclass() throws Exception {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
oneOf(clientHelper).toList(raw, MESSAGE_HEADER_LENGTH,
raw.length - MESSAGE_HEADER_LENGTH);
will(returnValue(body));
}});
@NotNullByDefault
BdfMessageValidator v = new BdfMessageValidator(clientHelper,
metadataEncoder, clock) {
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList b) throws InvalidMessageException, FormatException {
throw new FormatException();
}
};
v.validateMessage(message, group);
}
}

View File

@@ -0,0 +1,358 @@
package org.briarproject.bramble.client;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class ClientHelperImplTest extends BrambleTestCase {
private final Mockery context = new Mockery();
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
private final BdfReaderFactory bdfReaderFactory =
context.mock(BdfReaderFactory.class);
private final BdfWriterFactory bdfWriterFactory =
context.mock(BdfWriterFactory.class);
private final MetadataParser metadataParser =
context.mock(MetadataParser.class);
private final MetadataEncoder metadataEncoder =
context.mock(MetadataEncoder.class);
private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class);
private final ClientHelper clientHelper;
private final GroupId groupId = new GroupId(getRandomId());
private final BdfDictionary dictionary = new BdfDictionary();
private final long timestamp = 42L;
private final byte[] rawMessage = getRandomBytes(42);
private final MessageId messageId = new MessageId(getRandomId());
private final Message message =
new Message(messageId, groupId, timestamp, rawMessage);
private final Metadata metadata = new Metadata();
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
private final String label = TestUtils.getRandomString(5);
public ClientHelperImplTest() {
clientHelper =
new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
}
@Test
public void testAddLocalMessage() throws Exception {
final boolean shared = true;
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(metadata));
oneOf(db).addLocalMessage(txn, message, metadata, shared);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
clientHelper.addLocalMessage(message, dictionary, shared);
context.assertIsSatisfied();
}
@Test
public void testCreateMessage() throws Exception {
final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{
oneOf(messageFactory).createMessage(groupId, timestamp, bytes);
}});
clientHelper.createMessage(groupId, timestamp, list);
context.assertIsSatisfied();
}
@Test
public void testGetMessageAsList() throws Exception {
final Transaction txn = new Transaction(null, true);
expectToList(true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getRawMessage(txn, messageId);
will(returnValue(rawMessage));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
clientHelper.getMessageAsList(messageId);
context.assertIsSatisfied();
}
@Test
public void testGetGroupMetadataAsDictionary() throws Exception {
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getGroupMetadata(txn, groupId);
will(returnValue(metadata));
oneOf(metadataParser).parse(metadata);
will(returnValue(dictionary));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(dictionary,
clientHelper.getGroupMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@Test
public void testGetMessageMetadataAsDictionary() throws Exception {
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getMessageMetadata(txn, messageId);
will(returnValue(metadata));
oneOf(metadataParser).parse(metadata);
will(returnValue(dictionary));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(dictionary,
clientHelper.getMessageMetadataAsDictionary(messageId));
context.assertIsSatisfied();
}
@Test
public void testGetMessageMetadataAsDictionaryMap() throws Exception {
final Map<MessageId, BdfDictionary> map =
new HashMap<MessageId, BdfDictionary>();
map.put(messageId, dictionary);
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getMessageMetadata(txn, groupId);
will(returnValue(Collections.singletonMap(messageId, metadata)));
oneOf(metadataParser).parse(metadata);
will(returnValue(dictionary));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@Test
public void testGetMessageMetadataAsDictionaryQuery() throws Exception {
final Map<MessageId, BdfDictionary> map =
new HashMap<MessageId, BdfDictionary>();
map.put(messageId, dictionary);
final BdfDictionary query =
BdfDictionary.of(new BdfEntry("query", "me"));
final Metadata queryMetadata = new Metadata();
queryMetadata.put("query", getRandomBytes(42));
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(metadataEncoder).encode(query);
will(returnValue(queryMetadata));
oneOf(db).getMessageMetadata(txn, groupId, queryMetadata);
will(returnValue(Collections.singletonMap(messageId, metadata)));
oneOf(metadataParser).parse(metadata);
will(returnValue(dictionary));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId, query));
context.assertIsSatisfied();
}
@Test
public void testMergeGroupMetadata() throws Exception {
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(metadata));
oneOf(db).mergeGroupMetadata(txn, groupId, metadata);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
clientHelper.mergeGroupMetadata(groupId, dictionary);
context.assertIsSatisfied();
}
@Test
public void testMergeMessageMetadata() throws Exception {
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(metadata));
oneOf(db).mergeMessageMetadata(txn, messageId, metadata);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
clientHelper.mergeMessageMetadata(messageId, dictionary);
context.assertIsSatisfied();
}
@Test
public void testToByteArray() throws Exception {
byte[] bytes = expectToByteArray(list);
assertArrayEquals(bytes, clientHelper.toByteArray(list));
context.assertIsSatisfied();
}
@Test
public void testToList() throws Exception {
expectToList(true);
assertEquals(list, clientHelper.toList(rawMessage));
context.assertIsSatisfied();
}
@Test
public void testToListWithNoEof() throws Exception {
expectToList(false); // no EOF after list
try {
clientHelper.toList(rawMessage);
fail();
} catch (FormatException e) {
// expected
context.assertIsSatisfied();
}
}
@Test
public void testSign() throws Exception {
final byte[] privateKey = getRandomBytes(42);
final byte[] signed = getRandomBytes(42);
final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{
oneOf(cryptoComponent).sign(label, bytes, privateKey);
will(returnValue(signed));
}});
assertArrayEquals(signed, clientHelper.sign(label, list, privateKey));
context.assertIsSatisfied();
}
@Test
public void testVerifySignature() throws Exception {
final byte[] publicKey = getRandomBytes(42);
final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
will(returnValue(true));
}});
clientHelper.verifySignature(label, rawMessage, publicKey, list);
context.assertIsSatisfied();
}
@Test
public void testVerifyWrongSignature() throws Exception {
final byte[] publicKey = getRandomBytes(42);
final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
will(returnValue(false));
}});
try {
clientHelper
.verifySignature(label, rawMessage, publicKey, list);
fail();
} catch (GeneralSecurityException e) {
// expected
context.assertIsSatisfied();
}
}
private byte[] expectToByteArray(final BdfList list) throws Exception {
final BdfWriter bdfWriter = context.mock(BdfWriter.class);
context.checking(new Expectations() {{
oneOf(bdfWriterFactory)
.createWriter(with(any(ByteArrayOutputStream.class)));
will(returnValue(bdfWriter));
oneOf(bdfWriter).writeList(list);
}});
return new byte[0];
}
private void expectToList(final boolean eof) throws Exception {
final BdfReader bdfReader = context.mock(BdfReader.class);
context.checking(new Expectations() {{
oneOf(bdfReaderFactory)
.createReader(with(any(InputStream.class)));
will(returnValue(bdfReader));
oneOf(bdfReader).readList();
will(returnValue(list));
oneOf(bdfReader).eof();
will(returnValue(eof));
}});
}
}

View File

@@ -0,0 +1,192 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.BrambleMockTestCase;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.transport.KeyManager;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class ContactManagerImplTest extends BrambleMockTestCase {
private final Mockery context = new Mockery();
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final KeyManager keyManager = context.mock(KeyManager.class);
private final ContactManager contactManager;
private final ContactId contactId = new ContactId(42);
private final Author remote =
new Author(new AuthorId(getRandomId()), "remote",
getRandomBytes(42));
private final AuthorId local = new AuthorId(getRandomId());
private final boolean verified = false, active = true;
private final Contact contact =
new Contact(contactId, remote, local, verified, active);
public ContactManagerImplTest() {
contactManager = new ContactManagerImpl(db, keyManager);
}
@Test
public void testAddContact() throws Exception {
final SecretKey master = getSecretKey();
final long timestamp = 42;
final boolean alice = true;
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).addContact(txn, remote, local, verified, active);
will(returnValue(contactId));
oneOf(keyManager)
.addContact(txn, contactId, master, timestamp, alice);
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(contactId, contactManager
.addContact(remote, local, master, timestamp, alice, verified,
active));
}
@Test
public void testGetContact() throws Exception {
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(contact, contactManager.getContact(contactId));
}
@Test
public void testGetContactByAuthor() throws Exception {
final Transaction txn = new Transaction(null, true);
final Collection<Contact> contacts = Collections.singleton(contact);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contacts));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(contact, contactManager.getContact(remote.getId(), local));
}
@Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownAuthor() throws Exception {
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(Collections.emptyList()));
oneOf(db).endTransaction(txn);
}});
contactManager.getContact(remote.getId(), local);
}
@Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownLocalAuthor() throws Exception {
final Transaction txn = new Transaction(null, true);
final Collection<Contact> contacts = Collections.singleton(contact);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contacts));
oneOf(db).endTransaction(txn);
}});
contactManager.getContact(remote.getId(), new AuthorId(getRandomId()));
}
@Test
public void testActiveContacts() throws Exception {
Collection<Contact> activeContacts = Collections.singletonList(contact);
final Collection<Contact> contacts =
new ArrayList<Contact>(activeContacts);
contacts.add(new Contact(new ContactId(3), remote, local, true, false));
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(activeContacts, contactManager.getActiveContacts());
}
@Test
public void testRemoveContact() throws Exception {
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContact(txn, contactId);
will(returnValue(contact));
oneOf(db).removeContact(txn, contactId);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
contactManager.removeContact(contactId);
}
@Test
public void testSetContactActive() throws Exception {
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(db).setContactActive(txn, contactId, active);
}});
contactManager.setContactActive(txn, contactId, active);
}
@Test
public void testContactExists() throws Exception {
final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).containsContact(txn, remote.getId(), local);
will(returnValue(true));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertTrue(contactManager.contactExists(remote.getId(), local));
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class AsciiArmourTest extends BrambleTestCase {
@Test
public void testWrapOnSingleLine() {
byte[] b = new byte[8];
for (int i = 0; i < b.length; i++) b[i] = (byte) i;
String expected = "0001020304050607\r\n";
assertEquals(expected, AsciiArmour.wrap(b, 70));
}
@Test
public void testWrapOnMultipleLines() {
byte[] b = new byte[8];
for (int i = 0; i < b.length; i++) b[i] = (byte) i;
String expected = "0001020\r\n3040506\r\n07\r\n";
assertEquals(expected, AsciiArmour.wrap(b, 7));
}
@Test
public void testUnwrapOnSingleLine() throws Exception {
String s = "0001020304050607";
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
assertArrayEquals(expected, AsciiArmour.unwrap(s));
}
@Test
public void testUnwrapOnMultipleLines() throws Exception {
String s = "0001020\r\n3040506\r\n07";
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
assertArrayEquals(expected, AsciiArmour.unwrap(s));
}
@Test
public void testUnwrapWithJunkCharacters() throws Exception {
String s = "0001??020\rzz\n30z40..506\r\n07;;";
byte[] expected = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
assertArrayEquals(expected, AsciiArmour.unwrap(s));
}
}

View File

@@ -0,0 +1,172 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
public class Blake2sDigestTest extends BrambleTestCase {
// Vectors from BLAKE2 web site: https://blake2.net/blake2s-test.txt
private static final String[][] keyedTestVectors = {
// input/message, key, hash
{
"",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49",
},
{
"00",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1",
},
{
"0001",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"172ffc67153d12e0ca76a8b6cd5d4731885b39ce0cac93a8972a18006c8b8baf",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"4f8ce1e51d2fe7f24043a904d898ebfc91975418753413aa099b795ecb35cedb",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd",
},
};
@Test
public void testDigestWithKeyedTestVectors() {
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
keyedTestVectors[0][1]));
for (String[] keyedTestVector : keyedTestVectors) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
digest.reset();
digest.update(input, 0, input.length);
byte[] hash = new byte[32];
digest.doFinal(hash, 0);
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
hash);
}
}
@Test
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
keyedTestVectors[0][1]));
Random random = new Random();
for (int i = 0; i < 100; i++) {
for (String[] keyedTestVector : keyedTestVectors) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
if (input.length < 3) continue;
digest.reset();
int pos = random.nextInt(input.length);
if (pos > 0)
digest.update(input, 0, pos);
digest.update(input[pos]);
if (pos < (input.length - 1))
digest.update(input, pos + 1, input.length - (pos + 1));
byte[] hash = new byte[32];
digest.doFinal(hash, 0);
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
hash);
}
}
}
@Test
public void testReset() {
// Generate a non-zero key
byte[] key = new byte[32];
for (byte i = 0; i < key.length; i++) key[i] = i;
// Generate some non-zero input longer than the key
byte[] input = new byte[key.length + 1];
for (byte i = 0; i < input.length; i++) input[i] = i;
// Hash the input
Blake2sDigest digest = new Blake2sDigest(key);
digest.update(input, 0, input.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
// Create a second instance, hash the input without calling doFinal()
Blake2sDigest digest1 = new Blake2sDigest(key);
digest1.update(input, 0, input.length);
// Reset the second instance and hash the input again
digest1.reset();
digest1.update(input, 0, input.length);
byte[] hash1 = new byte[digest.getDigestSize()];
digest1.doFinal(hash1, 0);
// The hashes should be identical
assertArrayEquals(hash, hash1);
}
// Self-test routine from https://tools.ietf.org/html/rfc7693#appendix-E
private static final String SELF_TEST_RESULT =
"6A411F08CE25ADCDFB02ABA641451CEC53C598B24F4FC787FBDC88797F4C1DFE";
private static final int[] SELF_TEST_DIGEST_LEN = {16, 20, 28, 32};
private static final int[] SELF_TEST_INPUT_LEN = {0, 3, 64, 65, 255, 1024};
private static byte[] selfTestSequence(int len, int seed) {
int a = 0xDEAD4BAD * seed;
int b = 1;
int t;
byte[] out = new byte[len];
for (int i = 0; i < len; i++) {
t = a + b;
a = b;
b = t;
out[i] = (byte) ((t >> 24) & 0xFF);
}
return out;
}
@Test
public void runSelfTest() {
Blake2sDigest testDigest = new Blake2sDigest();
byte[] md = new byte[32];
for (int i = 0; i < 4; i++) {
int outlen = SELF_TEST_DIGEST_LEN[i];
for (int j = 0; j < 6; j++) {
int inlen = SELF_TEST_INPUT_LEN[j];
// unkeyed hash
byte[] in = selfTestSequence(inlen, inlen);
Blake2sDigest unkeyedDigest = new Blake2sDigest(outlen * 8);
unkeyedDigest.update(in, 0, inlen);
unkeyedDigest.doFinal(md, 0);
// hash the hash
testDigest.update(md, 0, outlen);
// keyed hash
byte[] key = selfTestSequence(outlen, outlen);
Blake2sDigest keyedDigest = new Blake2sDigest(key, outlen, null,
null);
keyedDigest.update(in, 0, inlen);
keyedDigest.doFinal(md, 0);
// hash the hash
testDigest.update(md, 0, outlen);
}
}
byte[] hash = new byte[32];
testDigest.doFinal(hash, 0);
assertArrayEquals(StringUtils.fromHexString(SELF_TEST_RESULT), hash);
}
}

View File

@@ -0,0 +1,110 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.junit.Assert.assertEquals;
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
@Test
public void testMultiplierProducesSameResultsAsDefault() throws Exception {
// Instantiate the default implementation of the curve
X9ECParameters defaultX9Parameters =
TeleTrusTNamedCurves.getByName("brainpoolp256r1");
ECCurve defaultCurve = defaultX9Parameters.getCurve();
ECPoint defaultG = defaultX9Parameters.getG();
BigInteger defaultN = defaultX9Parameters.getN();
BigInteger defaultH = defaultX9Parameters.getH();
// Check that the default parameters are equal to our parameters
assertEquals(PARAMETERS.getCurve(), defaultCurve);
assertEquals(PARAMETERS.getG(), defaultG);
assertEquals(PARAMETERS.getN(), defaultN);
assertEquals(PARAMETERS.getH(), defaultH);
// ECDomainParameters doesn't have an equals() method, but it's just a
// container for the parameters
ECDomainParameters defaultParameters = new ECDomainParameters(
defaultCurve, defaultG, defaultN, defaultH);
// Generate two key pairs with each set of parameters, using the same
// deterministic PRNG for both sets of parameters
byte[] seed = new byte[32];
new SecureRandom().nextBytes(seed);
// Montgomery ladder multiplier
SecureRandom random = new FortunaSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(PARAMETERS, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
montgomeryGenerator.init(montgomeryGeneratorParams);
AsymmetricCipherKeyPair montgomeryKeyPair1 =
montgomeryGenerator.generateKeyPair();
ECPrivateKeyParameters montgomeryPrivate1 =
(ECPrivateKeyParameters) montgomeryKeyPair1.getPrivate();
ECPublicKeyParameters montgomeryPublic1 =
(ECPublicKeyParameters) montgomeryKeyPair1.getPublic();
AsymmetricCipherKeyPair montgomeryKeyPair2 =
montgomeryGenerator.generateKeyPair();
ECPrivateKeyParameters montgomeryPrivate2 =
(ECPrivateKeyParameters) montgomeryKeyPair2.getPrivate();
ECPublicKeyParameters montgomeryPublic2 =
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
// Default multiplier
random = new FortunaSecureRandom(seed);
ECKeyGenerationParameters defaultGeneratorParams =
new ECKeyGenerationParameters(defaultParameters, random);
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
defaultGenerator.init(defaultGeneratorParams);
AsymmetricCipherKeyPair defaultKeyPair1 =
defaultGenerator.generateKeyPair();
ECPrivateKeyParameters defaultPrivate1 =
(ECPrivateKeyParameters) defaultKeyPair1.getPrivate();
ECPublicKeyParameters defaultPublic1 =
(ECPublicKeyParameters) defaultKeyPair1.getPublic();
AsymmetricCipherKeyPair defaultKeyPair2 =
defaultGenerator.generateKeyPair();
ECPrivateKeyParameters defaultPrivate2 =
(ECPrivateKeyParameters) defaultKeyPair2.getPrivate();
ECPublicKeyParameters defaultPublic2 =
(ECPublicKeyParameters) defaultKeyPair2.getPublic();
// The key pairs generated with both sets of parameters should be equal
assertEquals(montgomeryPrivate1.getD(), defaultPrivate1.getD());
assertEquals(montgomeryPublic1.getQ(), defaultPublic1.getQ());
assertEquals(montgomeryPrivate2.getD(), defaultPrivate2.getD());
assertEquals(montgomeryPublic2.getQ(), defaultPublic2.getQ());
// OK, all of the above was just sanity checks - now for the test!
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(montgomeryPrivate1);
BigInteger sharedSecretMontgomeryMontgomery =
agreement.calculateAgreement(montgomeryPublic2);
agreement.init(montgomeryPrivate1);
BigInteger sharedSecretMontgomeryDefault =
agreement.calculateAgreement(defaultPublic2);
agreement.init(defaultPrivate1);
BigInteger sharedSecretDefaultMontgomery =
agreement.calculateAgreement(montgomeryPublic2);
agreement.init(defaultPrivate1);
BigInteger sharedSecretDefaultDefault =
agreement.calculateAgreement(defaultPublic2);
// Shared secrets calculated with different multipliers should be equal
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretMontgomeryDefault);
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultMontgomery);
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultDefault);
}
}

View File

@@ -0,0 +1,138 @@
package org.briarproject.bramble.crypto;
import org.spongycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.crypto.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.DSAKCalculator;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
// Not a JUnit test
public class EllipticCurvePerformanceTest {
private static final SecureRandom random = new SecureRandom();
private static final int SAMPLES = 50;
private static final int BYTES_TO_SIGN = 1024;
private static final List<String> SEC_NAMES = Arrays.asList(
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
public static void main(String[] args) {
for (String name : SEC_NAMES) {
ECDomainParameters params =
convertParams(SECNamedCurves.getByName(name));
runTest(name + " default", params);
runTest(name + " constant", constantTime(params));
}
for (String name : BRAINPOOL_NAMES) {
ECDomainParameters params =
convertParams(TeleTrusTNamedCurves.getByName(name));
runTest(name + " default", params);
runTest(name + " constant", constantTime(params));
}
runTest("ours", EllipticCurveConstants.PARAMETERS);
}
private static void runTest(String name, ECDomainParameters params) {
// Generate two key pairs using the given parameters
ECKeyGenerationParameters generatorParams =
new ECKeyGenerationParameters(params, random);
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(generatorParams);
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
ECPublicKeyParameters public1 =
(ECPublicKeyParameters) keyPair1.getPublic();
ECPrivateKeyParameters private1 =
(ECPrivateKeyParameters) keyPair1.getPrivate();
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
ECPublicKeyParameters public2 =
(ECPublicKeyParameters) keyPair2.getPublic();
// Time some ECDH key agreements
List<Long> samples = new ArrayList<Long>();
for (int i = 0; i < SAMPLES; i++) {
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
long start = System.nanoTime();
agreement.init(private1);
agreement.calculateAgreement(public2);
samples.add(System.nanoTime() - start);
}
long agreementMedian = median(samples);
// Time some signatures
List<byte[]> signatures = new ArrayList<byte[]>();
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(true, new ParametersWithRandom(private1, random));
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
signatures.add(signer.generateSignature());
samples.add(System.nanoTime() - start);
}
long signatureMedian = median(samples);
// Time some signature verifications
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(false, public1);
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
if (!signer.verifySignature(signatures.get(i)))
throw new AssertionError();
samples.add(System.nanoTime() - start);
}
long verificationMedian = median(samples);
System.out.println(name + ": "
+ agreementMedian + " "
+ signatureMedian + " "
+ verificationMedian);
}
private static long median(List<Long> list) {
int size = list.size();
if (size == 0) throw new IllegalArgumentException();
Collections.sort(list);
if (size % 2 == 1) return list.get(size / 2);
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
}
private static ECDomainParameters convertParams(X9ECParameters in) {
return new ECDomainParameters(in.getCurve(), in.getG(), in.getN(),
in.getH());
}
private static ECDomainParameters constantTime(ECDomainParameters in) {
ECCurve curve = in.getCurve().configure().setMultiplier(
new MontgomeryLadderMultiplier()).create();
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(x, y);
return new ECDomainParameters(curve, g, in.getN(), in.getH());
}
}

View File

@@ -0,0 +1,99 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class FortunaGeneratorTest extends BrambleTestCase {
@Test
public void testCounterInitialisedToOne() {
FortunaGenerator f = new FortunaGenerator(new byte[32]);
// The counter is little-endian
byte[] expected = new byte[16];
expected[0] = 1;
assertArrayEquals(expected, f.getCounter());
}
@Test
public void testIncrementCounter() {
FortunaGenerator f = new FortunaGenerator(new byte[32]);
// Increment the counter until it reaches 255
for (int i = 1; i < 255; i++) f.incrementCounter();
byte[] expected = new byte[16];
expected[0] = (byte) 255;
assertArrayEquals(expected, f.getCounter());
// Increment the counter again - it should carry into the next byte
f.incrementCounter();
expected[0] = 0;
expected[1] = 1;
assertArrayEquals(expected, f.getCounter());
// Increment the counter until it carries into the next byte
for (int i = 256; i < 65536; i++) f.incrementCounter();
expected[0] = 0;
expected[1] = 0;
expected[2] = 1;
assertArrayEquals(expected, f.getCounter());
}
@Test
public void testNextBytes() {
// Generate several outputs with the same seed - they should all match
byte[] seed = new byte[32];
byte[] out1 = new byte[48];
new FortunaGenerator(seed).nextBytes(out1, 0, 48);
// One byte longer than a block, with an offset of one
byte[] out2 = new byte[49];
new FortunaGenerator(seed).nextBytes(out2, 1, 48);
for (int i = 0; i < 48; i++) assertEquals(out1[i], out2[i + 1]);
// One byte shorter than a block
byte[] out3 = new byte[47];
new FortunaGenerator(seed).nextBytes(out3, 0, 47);
for (int i = 0; i < 47; i++) assertEquals(out1[i], out3[i]);
// Less than a block, with an offset greater than a block
byte[] out4 = new byte[32];
new FortunaGenerator(seed).nextBytes(out4, 17, 15);
for (int i = 0; i < 15; i++) assertEquals(out1[i], out4[i + 17]);
}
@Test
public void testRekeying() {
byte[] seed = new byte[32];
FortunaGenerator f = new FortunaGenerator(seed);
// Generate three blocks of output
byte[] out1 = new byte[48];
f.nextBytes(out1, 0, 48);
// Create another generator with the same seed and generate one block
f = new FortunaGenerator(seed);
byte[] out2 = new byte[16];
f.nextBytes(out2, 0, 16);
// The generator should have rekeyed with the 2nd and 3rd blocks
byte[] expectedKey = new byte[32];
System.arraycopy(out1, 16, expectedKey, 0, 32);
// The generator's counter should have been incremented 3 times
byte[] expectedCounter = new byte[16];
expectedCounter[0] = 4;
// The next expected output block is the counter encrypted with the key
byte[] expectedOutput = new byte[16];
BlockCipher c = new AESLightEngine();
c.init(true, new KeyParameter(expectedKey));
c.processBlock(expectedCounter, 0, expectedOutput, 0);
// Check that the generator produces the expected output block
byte[] out3 = new byte[16];
f.nextBytes(out3, 0, 16);
assertArrayEquals(expectedOutput, out3);
}
@Test
public void testMaximumRequestLength() {
int expectedMax = 1024 * 1024;
byte[] output = new byte[expectedMax + 123];
FortunaGenerator f = new FortunaGenerator(new byte[32]);
assertEquals(expectedMax, f.nextBytes(output, 0, output.length));
}
}

View File

@@ -0,0 +1,67 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.params.KeyParameter;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_1;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_2;
import static org.briarproject.bramble.crypto.FortunaSecureRandom.SELF_TEST_VECTOR_3;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class FortunaSecureRandomTest extends BrambleTestCase {
@Test
public void testClassPassesSelfTest() {
assertTrue(FortunaSecureRandom.selfTest());
}
@Test
public void testSelfTestVectorsAreReproducible() {
byte[] key = new byte[32], seed = new byte[32];
byte[] counter = new byte[16], output = new byte[16];
byte[] newKey = new byte[32];
// Calculate the initial key
DoubleDigest digest = new DoubleDigest(new SHA256Digest());
digest.update(key);
digest.update(seed);
digest.digest(key, 0, 32);
// Calculate the first output block and the new key
BlockCipher c = new AESLightEngine();
c.init(true, new KeyParameter(key));
counter[0] = 1;
c.processBlock(counter, 0, output, 0);
counter[0] = 2;
c.processBlock(counter, 0, newKey, 0);
counter[0] = 3;
c.processBlock(counter, 0, newKey, 16);
System.arraycopy(newKey, 0, key, 0, 32);
// The first self-test vector should match the first output block
assertArrayEquals(SELF_TEST_VECTOR_1, output);
// Calculate the second output block and the new key before reseeding
c.init(true, new KeyParameter(key));
counter[0] = 4;
c.processBlock(counter, 0, output, 0);
counter[0] = 5;
c.processBlock(counter, 0, newKey, 0);
counter[0] = 6;
c.processBlock(counter, 0, newKey, 16);
System.arraycopy(newKey, 0, key, 0, 32);
// The second self-test vector should match the second output block
assertArrayEquals(SELF_TEST_VECTOR_2, output);
// Calculate the new key after reseeding
digest.update(key);
digest.update(seed);
digest.digest(key, 0, 32);
// Calculate the third output block
c.init(true, new KeyParameter(key));
counter[0] = 8;
c.processBlock(counter, 0, output, 0);
// The third self-test vector should match the third output block
assertArrayEquals(SELF_TEST_VECTOR_3, output);
}
}

View File

@@ -0,0 +1,40 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestSeedProvider;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.SeedProvider;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
public class KeyAgreementTest extends BrambleTestCase {
@Test
public void testDeriveMasterSecret() throws Exception {
SeedProvider seedProvider = new TestSeedProvider();
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();
byte[] bPub = bPair.getPublic().getEncoded();
SecretKey aMaster = crypto.deriveMasterSecret(aPub, bPair, true);
SecretKey bMaster = crypto.deriveMasterSecret(bPub, aPair, false);
assertArrayEquals(aMaster.getBytes(), bMaster.getBytes());
}
@Test
public void testDeriveSharedSecret() throws Exception {
SeedProvider seedProvider = new TestSeedProvider();
CryptoComponent crypto = new CryptoComponentImpl(seedProvider);
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();
byte[] bPub = bPair.getPublic().getEncoded();
SecretKey aShared = crypto.deriveSharedSecret(bPub, aPair, true);
SecretKey bShared = crypto.deriveSharedSecret(aPub, bPair, false);
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
}
}

View File

@@ -0,0 +1,166 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestSeedProvider;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
public class KeyDerivationTest extends BrambleTestCase {
private final TransportId transportId = new TransportId("id");
private final CryptoComponent crypto;
private final SecretKey master;
public KeyDerivationTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
master = TestUtils.getSecretKey();
}
@Test
public void testKeysAreDistinct() {
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
assertAllDifferent(k);
}
@Test
public void testCurrentKeysMatchCurrentKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(),
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
// Alice's outgoing keys should equal Bob's incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getCurrentIncomingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
// Rotate into the future
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getCurrentIncomingKeys().getHeaderKey().getBytes(),
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
// Alice's outgoing keys should equal Bob's incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getCurrentIncomingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
}
@Test
public void testPreviousKeysMatchPreviousKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
// Alice's previous incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
// Compare Alice's current keys in period 456 with Bob's previous keys
// in period 457
kB = crypto.rotateTransportKeys(kB, 457);
// Alice's outgoing keys should equal Bob's previous incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getPreviousIncomingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getPreviousIncomingKeys().getHeaderKey().getBytes());
}
@Test
public void testNextKeysMatchNextKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in
// period 455
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
// Alice's outgoing keys should equal Bob's next incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getNextIncomingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getNextIncomingKeys().getHeaderKey().getBytes());
// Compare Alice's next keys in period 456 with Bob's current keys
// in period 457
kB = crypto.rotateTransportKeys(kB, 457);
// Alice's next incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
assertArrayEquals(kA.getNextIncomingKeys().getHeaderKey().getBytes(),
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
}
@Test
public void testMasterKeyAffectsOutput() {
SecretKey master1 = TestUtils.getSecretKey();
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
123, true);
assertAllDifferent(k, k1);
}
@Test
public void testTransportIdAffectsOutput() {
TransportId transportId1 = new TransportId("id1");
assertFalse(transportId.getString().equals(transportId1.getString()));
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
123, true);
assertAllDifferent(k, k1);
}
private void assertAllDifferent(TransportKeys... transportKeys) {
List<SecretKey> secretKeys = new ArrayList<SecretKey>();
for (TransportKeys k : transportKeys) {
secretKeys.add(k.getPreviousIncomingKeys().getTagKey());
secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey());
secretKeys.add(k.getCurrentIncomingKeys().getTagKey());
secretKeys.add(k.getCurrentIncomingKeys().getHeaderKey());
secretKeys.add(k.getNextIncomingKeys().getTagKey());
secretKeys.add(k.getNextIncomingKeys().getHeaderKey());
secretKeys.add(k.getCurrentOutgoingKeys().getTagKey());
secretKeys.add(k.getCurrentOutgoingKeys().getHeaderKey());
}
assertAllDifferent(secretKeys);
}
private void assertAllDifferent(List<SecretKey> keys) {
for (SecretKey ki : keys) {
for (SecretKey kj : keys) {
if (ki == kj) assertArrayEquals(ki.getBytes(), kj.getBytes());
else assertFalse(Arrays.equals(ki.getBytes(), kj.getBytes()));
}
}
}
}

View File

@@ -0,0 +1,170 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestSeedProvider;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class KeyEncodingAndParsingTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSeedProvider());
@Test
public void testAgreementPublicKeyLength() throws Exception {
// Generate 10 agreement key pairs
for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@Test
public void testAgreementPublicKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getAgreementKeyParser();
// Generate two key pairs
KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair();
// Derive the shared secret
PublicKey aPub = aPair.getPublic();
byte[] secret = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
// Encode and parse the public key - no exceptions should be thrown
aPub = parser.parsePublicKey(aPub.getEncoded());
aPub = parser.parsePublicKey(aPub.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
assertArrayEquals(secret, secret1);
}
@Test
public void testAgreementPrivateKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getAgreementKeyParser();
// Generate two key pairs
KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair();
// Derive the shared secret
PrivateKey bPriv = bPair.getPrivate();
byte[] secret = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
// Encode and parse the private key - no exceptions should be thrown
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
assertArrayEquals(secret, secret1);
}
@Test
public void testAgreementKeyParserByFuzzing() throws Exception {
KeyParser parser = crypto.getAgreementKeyParser();
// Generate a key pair to get the proper public key length
KeyPair p = crypto.generateAgreementKeyPair();
int pubLength = p.getPublic().getEncoded().length;
int privLength = p.getPrivate().getEncoded().length;
// Parse some random byte arrays - expect GeneralSecurityException
for (int i = 0; i < 1000; i++) {
try {
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
} catch (GeneralSecurityException expected) {
// Expected
}
try {
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
} catch (GeneralSecurityException expected) {
// Expected
}
}
}
@Test
public void testSignaturePublicKeyLength() throws Exception {
// Generate 10 signature key pairs
for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@Test
public void testSignatureLength() throws Exception {
// Generate 10 signature key pairs
for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] key = keyPair.getPrivate().getEncoded();
// Sign some random data and check the length of the signature
byte[] toBeSigned = TestUtils.getRandomBytes(1234);
byte[] signature = crypto.sign("label", toBeSigned, key);
assertTrue(signature.length <= MAX_SIGNATURE_LENGTH);
}
}
@Test
public void testSignaturePublicKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate two key pairs
KeyPair aPair = crypto.generateSignatureKeyPair();
KeyPair bPair = crypto.generateSignatureKeyPair();
// Derive the shared secret
PublicKey aPub = aPair.getPublic();
byte[] secret = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
// Encode and parse the public key - no exceptions should be thrown
aPub = parser.parsePublicKey(aPub.getEncoded());
aPub = parser.parsePublicKey(aPub.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
assertArrayEquals(secret, secret1);
}
@Test
public void testSignaturePrivateKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate two key pairs
KeyPair aPair = crypto.generateSignatureKeyPair();
KeyPair bPair = crypto.generateSignatureKeyPair();
// Derive the shared secret
PrivateKey bPriv = bPair.getPrivate();
byte[] secret = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
// Encode and parse the private key - no exceptions should be thrown
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
assertArrayEquals(secret, secret1);
}
@Test
public void testSignatureKeyParserByFuzzing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate a key pair to get the proper public key length
KeyPair p = crypto.generateSignatureKeyPair();
int pubLength = p.getPublic().getEncoded().length;
int privLength = p.getPrivate().getEncoded().length;
// Parse some random byte arrays - expect GeneralSecurityException
for (int i = 0; i < 1000; i++) {
try {
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
} catch (GeneralSecurityException expected) {
// Expected
}
try {
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
} catch (GeneralSecurityException expected) {
// Expected
}
}
}
}

View File

@@ -0,0 +1,55 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestSeedProvider;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
public class MacTest extends BrambleTestCase {
private final CryptoComponent crypto;
private final SecretKey k = TestUtils.getSecretKey();
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
private final byte[] inputBytes2 = new byte[0];
public MacTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
}
@Test
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
// Calculate the MAC twice - the results should be identical
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
assertArrayEquals(mac, mac1);
}
@Test
public void testDifferentKeysProduceDifferentMacs() {
// Generate second random key
SecretKey k1 = TestUtils.getSecretKey();
// Calculate the MAC with each key - the results should be different
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k1, inputBytes, inputBytes1, inputBytes2);
assertFalse(Arrays.equals(mac, mac1));
}
@Test
public void testDifferentInputsProduceDifferentMacs() {
// Calculate the MAC with the inputs in different orders - the results
// should be different
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k, inputBytes2, inputBytes1, inputBytes);
assertFalse(Arrays.equals(mac, mac1));
}
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.junit.Test;
import org.spongycastle.crypto.CryptoException;
import java.security.SecureRandom;
import static org.junit.Assert.assertArrayEquals;
public class MessageEncrypterTest extends BrambleTestCase {
private final SecureRandom random = new SecureRandom();
@Test
public void testEncryptionAndDecryption() throws Exception {
MessageEncrypter m = new MessageEncrypter(random);
KeyPair kp = m.generateKeyPair();
PublicKey pub = kp.getPublic();
PrivateKey priv = kp.getPrivate();
byte[] plaintext = new byte[123];
random.nextBytes(plaintext);
byte[] ciphertext = m.encrypt(pub, plaintext);
byte[] decrypted = m.decrypt(priv, ciphertext);
assertArrayEquals(plaintext, decrypted);
}
@Test(expected = CryptoException.class)
public void testDecryptionFailsWithAlteredCiphertext() throws Exception {
MessageEncrypter m = new MessageEncrypter(random);
KeyPair kp = m.generateKeyPair();
PublicKey pub = kp.getPublic();
PrivateKey priv = kp.getPrivate();
byte[] ciphertext = m.encrypt(pub, new byte[123]);
ciphertext[random.nextInt(ciphertext.length)] ^= 0xFF;
m.decrypt(priv, ciphertext);
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestSeedProvider;
import org.briarproject.bramble.TestUtils;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class PasswordBasedKdfTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSeedProvider());
@Test
public void testEncryptionAndDecryption() {
byte[] input = TestUtils.getRandomBytes(1234);
String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password);
byte[] output = crypto.decryptWithPassword(ciphertext, password);
assertArrayEquals(input, output);
}
@Test
public void testInvalidCiphertextReturnsNull() {
byte[] input = TestUtils.getRandomBytes(1234);
String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password);
// Modify the ciphertext
int position = new Random().nextInt(ciphertext.length);
ciphertext[position] = (byte) (ciphertext[position] ^ 0xFF);
byte[] output = crypto.decryptWithPassword(ciphertext, password);
assertNull(output);
}
@Test
public void testCalibration() {
// If the target time is unachievable, one iteration should be used
int iterations = crypto.chooseIterationCount(0);
assertEquals(1, iterations);
// If the target time is long, more than one iteration should be used
iterations = crypto.chooseIterationCount(10 * 1000);
assertTrue(iterations > 1);
// If the target time is very long, max iterations should be used
iterations = crypto.chooseIterationCount(Integer.MAX_VALUE);
assertEquals(Integer.MAX_VALUE, iterations);
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.junit.Test;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
import static org.junit.Assert.assertTrue;
public class PasswordStrengthEstimatorImplTest extends BrambleTestCase {
@Test
public void testWeakPasswords() {
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
assertTrue(e.estimateStrength("") < QUITE_STRONG);
assertTrue(e.estimateStrength("password") < QUITE_STRONG);
assertTrue(e.estimateStrength("letmein") < QUITE_STRONG);
assertTrue(e.estimateStrength("123456") < QUITE_STRONG);
}
@Test
public void testStrongPasswords() {
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
// Industry standard
assertTrue(e.estimateStrength("Tr0ub4dor&3") > QUITE_STRONG);
assertTrue(e.estimateStrength("correcthorsebatterystaple")
> QUITE_STRONG);
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestSeedProvider;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SignatureTest extends BrambleTestCase {
private final CryptoComponent crypto;
private final byte[] publicKey, privateKey;
private final String label = TestUtils.getRandomString(42);
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
public SignatureTest() {
crypto = new CryptoComponentImpl(new TestSeedProvider());
KeyPair k = crypto.generateSignatureKeyPair();
publicKey = k.getPublic().getEncoded();
privateKey = k.getPrivate().getEncoded();
}
@Test
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
throws Exception {
// Calculate the Signature twice - the results should be identical
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes, privateKey);
assertArrayEquals(sig1, sig2);
}
@Test
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
// Generate second private key
KeyPair k2 = crypto.generateSignatureKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded();
// Calculate the signature with each key
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes, privateKey2);
assertFalse(Arrays.equals(sig1, sig2));
}
@Test
public void testDifferentInputsProduceDifferentSignatures()
throws Exception {
// Generate a second input
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
// Calculate the signature with different inputs
// the results should be different
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes2, privateKey);
assertFalse(Arrays.equals(sig1, sig2));
}
@Test
public void testDifferentLabelsProduceDifferentSignatures()
throws Exception {
// Generate a second label
String label2 = TestUtils.getRandomString(42);
// Calculate the signature with different inputs
// the results should be different
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label2, inputBytes, privateKey);
assertFalse(Arrays.equals(sig1, sig2));
}
@Test
public void testSignatureVerification() throws Exception {
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertTrue(crypto.verify(label, inputBytes, publicKey, sig));
}
@Test
public void testDifferentKeyFailsVerification() throws Exception {
// Generate second private key
KeyPair k2 = crypto.generateSignatureKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded();
// calculate the signature with different key, should fail to verify
byte[] sig = crypto.sign(label, inputBytes, privateKey2);
assertFalse(crypto.verify(label, inputBytes, publicKey, sig));
}
@Test
public void testDifferentInputFailsVerification() throws Exception {
// Generate a second input
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
// calculate the signature with different input, should fail to verify
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertFalse(crypto.verify(label, inputBytes2, publicKey, sig));
}
@Test
public void testDifferentLabelFailsVerification() throws Exception {
// Generate a second label
String label2 = TestUtils.getRandomString(42);
// calculate the signature with different label, should fail to verify
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertFalse(crypto.verify(label2, inputBytes, publicKey, sig));
}
}

View File

@@ -0,0 +1,197 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import static junit.framework.Assert.assertEquals;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.junit.Assert.assertArrayEquals;
public class StreamDecrypterImplTest extends BrambleTestCase {
private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey, frameKey;
private final byte[] streamHeaderIv, payload;
private final int payloadLength = 123, paddingLength = 234;
private final long streamNumber = 1234;
public StreamDecrypterImplTest() {
cipher = new TestAuthenticatedCipher(); // Null cipher
streamHeaderKey = TestUtils.getSecretKey();
frameKey = TestUtils.getSecretKey();
streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
payload = TestUtils.getRandomBytes(payloadLength);
}
@Test
public void testReadValidFrames() throws Exception {
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
int payloadLength1 = 345, paddingLength1 = 456;
FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
paddingLength1);
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader1);
out.write(payload1);
out.write(new byte[paddingLength1]);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Read the first frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
assertEquals(payloadLength, s.readFrame(buffer));
assertArrayStartsWith(payload, buffer, payloadLength);
// Read the second frame
assertEquals(payloadLength1, s.readFrame(buffer));
assertArrayStartsWith(payload1, buffer, payloadLength1);
// End of stream
assertEquals(-1, s.readFrame(buffer));
}
@Test(expected = IOException.class)
public void testTruncatedFrameThrowsException() throws Exception {
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH - 1]); // Chop off the last byte
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Try to read the truncated frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
s.readFrame(buffer);
}
@Test(expected = IOException.class)
public void testInvalidPayloadAndPaddingLengthThrowsException()
throws Exception {
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
// The payload length plus padding length is invalid
int payloadLength = MAX_PAYLOAD_LENGTH - 1, paddingLength = 2;
ByteUtils.writeUint16(payloadLength, frameHeader, 0);
ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES);
byte[] payload = TestUtils.getRandomBytes(payloadLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Try to read the invalid frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
s.readFrame(buffer);
}
@Test(expected = IOException.class)
public void testNonZeroPaddingThrowsException() throws Exception {
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
paddingLength);
// Set one of the padding bytes non-zero
byte[] padding = new byte[paddingLength];
padding[paddingLength - 1] = 1;
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(padding);
out.write(new byte[MAC_LENGTH]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Try to read the invalid frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
s.readFrame(buffer);
}
@Test
public void testCannotReadBeyondFinalFrame() throws Exception {
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(frameHeader, true, payloadLength,
paddingLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(streamHeaderIv);
out.write(frameKey.getBytes());
out.write(new byte[MAC_LENGTH]);
out.write(frameHeader);
out.write(payload);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
// Add some data beyond the final frame
out.write(new byte[1024]);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
streamNumber, streamHeaderKey);
// Read the first frame
byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
assertEquals(payloadLength, s.readFrame(buffer));
assertArrayStartsWith(payload, buffer, payloadLength);
// End of stream
assertEquals(-1, s.readFrame(buffer));
// Yup, definitely end of stream
assertEquals(-1, s.readFrame(buffer));
}
private static void assertArrayStartsWith(byte[] expected, byte[] actual,
int len) {
byte[] prefix = new byte[len];
System.arraycopy(actual, 0, prefix, 0, len);
assertArrayEquals(expected, prefix);
}
}

View File

@@ -0,0 +1,315 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
public class StreamEncrypterImplTest extends BrambleTestCase {
private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey, frameKey;
private final byte[] tag, streamHeaderIv, payload;
private final long streamNumber = 1234;
private final int payloadLength = 123, paddingLength = 234;
public StreamEncrypterImplTest() {
cipher = new TestAuthenticatedCipher(); // Null cipher
streamHeaderKey = TestUtils.getSecretKey();
frameKey = TestUtils.getSecretKey();
tag = TestUtils.getRandomBytes(TAG_LENGTH);
streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
payload = TestUtils.getRandomBytes(payloadLength);
}
@Test
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, 0, false);
// Expect the tag, stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWriteUnpaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, 0, true);
// Expect the tag, stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, 0, false);
// Expect the stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, 0, true);
// Expect the stream header, frame header, payload and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength, 0);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWritePaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, paddingLength, false);
// Expect the tag, stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWritePaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, paddingLength, true);
// Expect the tag, stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWritePaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, paddingLength, false);
// Expect the stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWritePaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
s.writeFrame(payload, payloadLength, paddingLength, true);
// Expect the stream header, frame header, payload, padding and MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, true, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testWriteTwoFramesWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
int payloadLength1 = 345, paddingLength1 = 456;
byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
s.writeFrame(payload, payloadLength, paddingLength, false);
s.writeFrame(payload1, payloadLength1, paddingLength1, true);
// Expect the tag, stream header, first frame header, payload, padding,
// MAC, second frame header, payload, padding, MAC
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader, false, payloadLength,
paddingLength);
expected.write(expectedFrameHeader);
expected.write(payload);
expected.write(new byte[paddingLength]);
expected.write(new byte[MAC_LENGTH]);
byte[] expectedFrameHeader1 = new byte[FRAME_HEADER_LENGTH];
FrameEncoder.encodeHeader(expectedFrameHeader1, true, payloadLength1,
paddingLength1);
expected.write(expectedFrameHeader1);
expected.write(payload1);
expected.write(new byte[paddingLength1]);
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testFlushWritesTagAndStreamHeaderIfNotAlreadyWritten()
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
// Flush the stream once
s.flush();
// Expect the tag and stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testFlushDoesNotWriteTagOrStreamHeaderIfAlreadyWritten()
throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
// Flush the stream twice
s.flush();
s.flush();
// Expect the tag and stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(tag);
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
@Test
public void testFlushDoesNotWriteTagIfNull() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
// Flush the stream once
s.flush();
// Expect the stream header
ByteArrayOutputStream expected = new ByteArrayOutputStream();
expected.write(streamHeaderIv);
expected.write(frameKey.getBytes());
expected.write(new byte[MAC_LENGTH]);
assertArrayEquals(expected.toByteArray(), out.toByteArray());
}
}

View File

@@ -0,0 +1,43 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
@NotNullByDefault
class TestAuthenticatedCipher implements AuthenticatedCipher {
private boolean encrypt = false;
@Override
public void init(boolean encrypt, SecretKey key, byte[] iv)
throws GeneralSecurityException {
this.encrypt = encrypt;
}
@Override
public int process(byte[] input, int inputOff, int len, byte[] output,
int outputOff) throws GeneralSecurityException {
if (encrypt) {
System.arraycopy(input, inputOff, output, outputOff, len);
for (int i = 0; i < MAC_LENGTH; i++)
output[outputOff + len + i] = 0;
return len + MAC_LENGTH;
} else {
for (int i = 0; i < MAC_LENGTH; i++)
if (input[inputOff + len - MAC_LENGTH + i] != 0)
throw new GeneralSecurityException();
System.arraycopy(input, inputOff, output, outputOff,
len - MAC_LENGTH);
return len - MAC_LENGTH;
}
}
@Override
public int getMacBytes() {
return MAC_LENGTH;
}
}

View File

@@ -0,0 +1,93 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import java.security.GeneralSecurityException;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
// Test vectors from the NaCl paper
// http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
private static final byte[] TEST_KEY = StringUtils.fromHexString(
"1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389");
private static final byte[] TEST_IV = StringUtils.fromHexString(
"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37");
private static final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
"be075fc53c81f2d5cf141316" +
"ebeb0c7b5228c52a4c62cbd4" +
"4b66849b64244ffce5ecbaaf" +
"33bd751a1ac728d45e6c6129" +
"6cdc3c01233561f41db66cce" +
"314adb310e3be8250c46f06d" +
"ceea3a7fa1348057e2f6556a" +
"d6b1318a024a838f21af1fde" +
"048977eb48f59ffd4924ca1c" +
"60902e52f0a089bc76897040" +
"e082f937763848645e0705");
private static final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
"f3ffc7703f9400e52a7dfb4b" +
"3d3305d98e993b9f48681273" +
"c29650ba32fc76ce48332ea7" +
"164d96a4476fb8c531a1186a" +
"c0dfc17c98dce87b4da7f011" +
"ec48c97271d2c20f9b928fe2" +
"270d6fb863d51738b48eeee3" +
"14a7cc8ab932164548e526ae" +
"90224368517acfeabd6bb373" +
"2bc0e9da99832b61ca01b6de" +
"56244a9e88d5f9b37973f622" +
"a43d14a6599b1f654cb45a74" +
"e355a5");
@Test
public void testEncrypt() throws Exception {
SecretKey k = new SecretKey(TEST_KEY);
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
cipher.init(true, k, TEST_IV);
byte[] output = new byte[TEST_CIPHERTEXT.length];
assertEquals(TEST_CIPHERTEXT.length, cipher.process(TEST_PLAINTEXT, 0,
TEST_PLAINTEXT.length, output, 0));
assertArrayEquals(TEST_CIPHERTEXT, output);
}
@Test
public void testDecrypt() throws Exception {
SecretKey k = new SecretKey(TEST_KEY);
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
cipher.init(false, k, TEST_IV);
byte[] output = new byte[TEST_PLAINTEXT.length];
assertEquals(TEST_PLAINTEXT.length, cipher.process(TEST_CIPHERTEXT, 0,
TEST_CIPHERTEXT.length, output, 0));
assertArrayEquals(TEST_PLAINTEXT, output);
}
@Test(expected = GeneralSecurityException.class)
public void testDecryptFailsWithShortInput() throws Exception {
SecretKey k = new SecretKey(TEST_KEY);
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
cipher.init(false, k, TEST_IV);
byte[] input = new byte[cipher.getMacBytes() - 1];
System.arraycopy(TEST_CIPHERTEXT, 0, input, 0, input.length);
byte[] output = new byte[TEST_PLAINTEXT.length];
cipher.process(input, 0, input.length, output, 0);
}
@Test(expected = GeneralSecurityException.class)
public void testDecryptFailsWithAlteredCiphertext() throws Exception {
SecretKey k = new SecretKey(TEST_KEY);
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
cipher.init(false, k, TEST_IV);
byte[] input = new byte[TEST_CIPHERTEXT.length];
System.arraycopy(TEST_CIPHERTEXT, 0, input, 0, TEST_CIPHERTEXT.length);
input[new Random().nextInt(TEST_CIPHERTEXT.length)] ^= 0xFF;
byte[] output = new byte[TEST_PLAINTEXT.length];
cipher.process(input, 0, input.length, output, 0);
}
}

View File

@@ -0,0 +1,545 @@
package org.briarproject.bramble.data;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.data.BdfReaderImpl.DEFAULT_NESTED_LIMIT;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BdfReaderImplTest extends BrambleTestCase {
private BdfReaderImpl r = null;
@Test
public void testReadEmptyInput() throws Exception {
setContents("");
assertTrue(r.eof());
}
@Test
public void testReadNull() throws Exception {
setContents("00");
r.readNull();
assertTrue(r.eof());
}
@Test
public void testSkipNull() throws Exception {
setContents("00");
r.skipNull();
assertTrue(r.eof());
}
@Test
public void testReadBoolean() throws Exception {
setContents("10" + "11");
assertFalse(r.readBoolean());
assertTrue(r.readBoolean());
assertTrue(r.eof());
}
@Test
public void testSkipBoolean() throws Exception {
setContents("10" + "11");
r.skipBoolean();
r.skipBoolean();
assertTrue(r.eof());
}
@Test
public void testReadLong8() throws Exception {
setContents("21" + "00" + "21" + "FF"
+ "21" + "7F" + "21" + "80");
assertEquals(0, r.readLong());
assertEquals(-1, r.readLong());
assertEquals(Byte.MAX_VALUE, r.readLong());
assertEquals(Byte.MIN_VALUE, r.readLong());
assertTrue(r.eof());
}
@Test
public void testSkipLong8() throws Exception {
setContents("21" + "00");
r.skipLong();
assertTrue(r.eof());
}
@Test
public void testReadLong16() throws Exception {
setContents("22" + "0080" + "22" + "FF7F"
+ "22" + "7FFF" + "22" + "8000");
assertEquals(Byte.MAX_VALUE + 1, r.readLong());
assertEquals(Byte.MIN_VALUE - 1, r.readLong());
assertEquals(Short.MAX_VALUE, r.readLong());
assertEquals(Short.MIN_VALUE, r.readLong());
assertTrue(r.eof());
}
@Test
public void testSkipLong16() throws Exception {
setContents("22" + "0080");
r.skipLong();
assertTrue(r.eof());
}
@Test
public void testReadLong32() throws Exception {
setContents("24" + "00008000" + "24" + "FFFF7FFF"
+ "24" + "7FFFFFFF" + "24" + "80000000");
assertEquals(Short.MAX_VALUE + 1, r.readLong());
assertEquals(Short.MIN_VALUE - 1, r.readLong());
assertEquals(Integer.MAX_VALUE, r.readLong());
assertEquals(Integer.MIN_VALUE, r.readLong());
assertTrue(r.eof());
}
@Test
public void testSkipLong32() throws Exception {
setContents("24" + "00008000");
r.skipLong();
assertTrue(r.eof());
}
@Test
public void testReadLong64() throws Exception {
setContents("28" + "0000000080000000" + "28" + "FFFFFFFF7FFFFFFF"
+ "28" + "7FFFFFFFFFFFFFFF" + "28" + "8000000000000000");
assertEquals(Integer.MAX_VALUE + 1L, r.readLong());
assertEquals(Integer.MIN_VALUE - 1L, r.readLong());
assertEquals(Long.MAX_VALUE, r.readLong());
assertEquals(Long.MIN_VALUE, r.readLong());
assertTrue(r.eof());
}
@Test
public void testSkipLong() throws Exception {
setContents("28" + "0000000080000000");
r.skipLong();
assertTrue(r.eof());
}
@Test
public void testReadDouble() throws Exception {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
// http://steve.hollasch.net/cgindex/coding/ieeefloat.html
setContents("38" + "0000000000000000" + "38" + "3FF0000000000000"
+ "38" + "4000000000000000" + "38" + "BFF0000000000000"
+ "38" + "8000000000000000" + "38" + "FFF0000000000000"
+ "38" + "7FF0000000000000" + "38" + "7FF8000000000000");
assertEquals(0, Double.compare(0.0, r.readDouble()));
assertEquals(0, Double.compare(1.0, r.readDouble()));
assertEquals(0, Double.compare(2.0, r.readDouble()));
assertEquals(0, Double.compare(-1.0, r.readDouble()));
assertEquals(0, Double.compare(-0.0, r.readDouble()));
assertEquals(0, Double.compare(Double.NEGATIVE_INFINITY,
r.readDouble()));
assertEquals(0, Double.compare(Double.POSITIVE_INFINITY,
r.readDouble()));
assertTrue(Double.isNaN(r.readDouble()));
assertTrue(r.eof());
}
@Test
public void testSkipFloat() throws Exception {
setContents("38" + "0000000000000000");
r.skipDouble();
assertTrue(r.eof());
}
@Test
public void testReadString8() throws Exception {
String longest = TestUtils.getRandomString(Byte.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// "foo", the empty string, and 127 random letters
setContents("41" + "03" + "666F6F" + "41" + "00" +
"41" + "7F" + longHex);
assertEquals("foo", r.readString(Integer.MAX_VALUE));
assertEquals("", r.readString(Integer.MAX_VALUE));
assertEquals(longest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadString8ChecksMaxLength() throws Exception {
// "foo" twice
setContents("41" + "03" + "666F6F" + "41" + "03" + "666F6F");
assertEquals("foo", r.readString(3));
assertTrue(r.hasString());
r.readString(2);
}
@Test
public void testSkipString8() throws Exception {
String longest = TestUtils.getRandomString(Byte.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// "foo", the empty string, and 127 random letters
setContents("41" + "03" + "666F6F" + "41" + "00" +
"41" + "7F" + longHex);
r.skipString();
r.skipString();
r.skipString();
assertTrue(r.eof());
}
@Test
public void testReadString16() throws Exception {
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = TestUtils.getRandomString(Short.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// 128 random letters and 2^15 -1 random letters
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
assertEquals(shortest, r.readString(Integer.MAX_VALUE));
assertEquals(longest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadString16ChecksMaxLength() throws Exception {
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 128 random letters, twice
setContents("42" + "0080" + shortHex + "42" + "0080" + shortHex);
assertEquals(shortest, r.readString(Byte.MAX_VALUE + 1));
assertTrue(r.hasString());
r.readString(Byte.MAX_VALUE);
}
@Test
public void testSkipString16() throws Exception {
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = TestUtils.getRandomString(Short.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
// 128 random letters and 2^15 - 1 random letters
setContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
r.skipString();
r.skipString();
assertTrue(r.eof());
}
@Test
public void testReadString32() throws Exception {
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters
setContents("44" + "00008000" + shortHex);
assertEquals(shortest, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadString32ChecksMaxLength() throws Exception {
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters, twice
setContents("44" + "00008000" + shortHex +
"44" + "00008000" + shortHex);
assertEquals(shortest, r.readString(Short.MAX_VALUE + 1));
assertTrue(r.hasString());
r.readString(Short.MAX_VALUE);
}
@Test
public void testSkipString32() throws Exception {
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
// 2^15 random letters, twice
setContents("44" + "00008000" + shortHex +
"44" + "00008000" + shortHex);
r.skipString();
r.skipString();
assertTrue(r.eof());
}
@Test
public void testReadUtf8String() throws Exception {
String unicode = "\uFDD0\uFDD1\uFDD2\uFDD3";
String hex = StringUtils.toHexString(unicode.getBytes("UTF-8"));
// STRING_8 tag, "foo", the empty string, and the test string
setContents("41" + "03" + "666F6F" + "41" + "00" + "41" + "0C" + hex);
assertEquals("foo", r.readString(Integer.MAX_VALUE));
assertEquals("", r.readString(Integer.MAX_VALUE));
assertEquals(unicode, r.readString(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test
public void testReadRaw8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// {1, 2, 3}, {}, and 127 zero bytes
setContents("51" + "03" + "010203" + "51" + "00" +
"51" + "7F" + longHex);
assertArrayEquals(new byte[] {1, 2, 3}, r.readRaw(Integer.MAX_VALUE));
assertArrayEquals(new byte[0], r.readRaw(Integer.MAX_VALUE));
assertArrayEquals(longest, r.readRaw(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadRaw8ChecksMaxLength() throws Exception {
// {1, 2, 3} twice
setContents("51" + "03" + "010203" + "51" + "03" + "010203");
assertArrayEquals(new byte[] {1, 2, 3}, r.readRaw(3));
assertTrue(r.hasRaw());
r.readRaw(2);
}
@Test
public void testSkipRaw8() throws Exception {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// {1, 2, 3}, {}, and 127 zero bytes
setContents("51" + "03" + "010203" + "51" + "00" +
"51" + "7F" + longHex);
r.skipRaw();
r.skipRaw();
r.skipRaw();
assertTrue(r.eof());
}
@Test
public void testReadRaw16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// 128 zero bytes and 2^15 - 1 zero bytes
setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
assertArrayEquals(shortest, r.readRaw(Integer.MAX_VALUE));
assertArrayEquals(longest, r.readRaw(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadRaw16ChecksMaxLength() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 128 zero bytes, twice
setContents("52" + "0080" + shortHex + "52" + "0080" + shortHex);
assertArrayEquals(shortest, r.readRaw(Byte.MAX_VALUE + 1));
assertTrue(r.hasRaw());
r.readRaw(Byte.MAX_VALUE);
}
@Test
public void testSkipRaw16() throws Exception {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
// 128 zero bytes and 2^15 - 1 zero bytes
setContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
r.skipRaw();
r.skipRaw();
assertTrue(r.eof());
}
@Test
public void testReadRaw32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes
setContents("54" + "00008000" + shortHex);
assertArrayEquals(shortest, r.readRaw(Integer.MAX_VALUE));
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testReadRaw32ChecksMaxLength() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes, twice
setContents("54" + "00008000" + shortHex +
"54" + "00008000" + shortHex);
assertArrayEquals(shortest, r.readRaw(Short.MAX_VALUE + 1));
assertTrue(r.hasRaw());
r.readRaw(Short.MAX_VALUE);
}
@Test
public void testSkipRaw32() throws Exception {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
// 2^15 zero bytes, twice
setContents("54" + "00008000" + shortHex +
"54" + "00008000" + shortHex);
r.skipRaw();
r.skipRaw();
assertTrue(r.eof());
}
@Test
public void testReadList() throws Exception {
// A list containing 1, "foo", and null
setContents("60" + "21" + "01" +
"41" + "03" + "666F6F" +
"00" + "80");
BdfList list = r.readList();
assertEquals(3, list.size());
assertEquals(1L, list.get(0));
assertEquals("foo", list.get(1));
assertEquals(NULL_VALUE, list.get(2));
}
@Test
public void testReadListManually() throws Exception {
// A list containing 1, "foo", and null
setContents("60" + "21" + "01" +
"41" + "03" + "666F6F" +
"00" + "80");
r.readListStart();
assertFalse(r.hasListEnd());
assertEquals(1, r.readLong());
assertFalse(r.hasListEnd());
assertEquals("foo", r.readString(1000));
assertFalse(r.hasListEnd());
assertTrue(r.hasNull());
r.readNull();
assertTrue(r.hasListEnd());
r.readListEnd();
assertTrue(r.eof());
}
@Test
public void testSkipList() throws Exception {
// A list containing 1, "foo", and 128
setContents("60" + "21" + "01" +
"41" + "03" + "666F6F" +
"22" + "0080" + "80");
r.skipList();
assertTrue(r.eof());
}
@Test
public void testReadDictionary() throws Exception {
// A dictionary containing "foo" -> 123 and "bar" -> null
setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
"41" + "03" + "626172" + "00" + "80");
BdfDictionary dictionary = r.readDictionary();
assertEquals(2, dictionary.size());
assertTrue(dictionary.containsKey("foo"));
assertEquals(123L, dictionary.get("foo"));
assertTrue(dictionary.containsKey("bar"));
assertEquals(NULL_VALUE, dictionary.get("bar"));
}
@Test
public void testReadDictionaryManually() throws Exception {
// A dictionary containing "foo" -> 123 and "bar" -> null
setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
"41" + "03" + "626172" + "00" + "80");
r.readDictionaryStart();
assertFalse(r.hasDictionaryEnd());
assertEquals("foo", r.readString(1000));
assertFalse(r.hasDictionaryEnd());
assertEquals(123, r.readLong());
assertFalse(r.hasDictionaryEnd());
assertEquals("bar", r.readString(1000));
assertFalse(r.hasDictionaryEnd());
assertTrue(r.hasNull());
r.readNull();
assertTrue(r.hasDictionaryEnd());
r.readDictionaryEnd();
assertTrue(r.eof());
}
@Test
public void testSkipDictionary() throws Exception {
// A map containing "foo" -> 123 and "bar" -> null
setContents("70" + "41" + "03" + "666F6F" + "21" + "7B" +
"41" + "03" + "626172" + "00" + "80");
r.skipDictionary();
assertTrue(r.eof());
}
@Test
public void testSkipNestedListsAndDictionaries() throws Exception {
// A list containing a dictionary containing "" -> an empty list
setContents("60" + "70" + "4100" + "60" + "80" + "80" + "80");
r.skipList();
assertTrue(r.eof());
}
@Test
public void testNestedListWithinDepthLimit() throws Exception {
// A list containing a list containing a list containing a list...
String lists = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) lists += "60";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++) lists += "80";
setContents(lists);
r.readList();
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testNestedListOutsideDepthLimit() throws Exception {
// A list containing a list containing a list containing a list...
String lists = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) lists += "60";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++) lists += "80";
setContents(lists);
r.readList();
}
@Test
public void testNestedDictionaryWithinDepthLimit() throws Exception {
// A dictionary containing a dictionary containing a dictionary...
String dicts = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++)
dicts += "70" + "41" + "03" + "666F6F";
dicts += "11";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT; i++)
dicts += "80";
setContents(dicts);
r.readDictionary();
assertTrue(r.eof());
}
@Test(expected = FormatException.class)
public void testNestedDictionaryOutsideDepthLimit() throws Exception {
// A dictionary containing a dictionary containing a dictionary...
String dicts = "";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++)
dicts += "70" + "41" + "03" + "666F6F";
dicts += "11";
for (int i = 1; i <= DEFAULT_NESTED_LIMIT + 1; i++)
dicts += "80";
setContents(dicts);
r.readDictionary();
}
@Test(expected = FormatException.class)
public void testOpenList() throws Exception {
// A list that is not closed
String list = "60";
setContents(list);
r.readList();
}
@Test(expected = FormatException.class)
public void testOpenDictionary() throws Exception {
// A dictionary that is not closed
String dicts = "70" + "41" + "03" + "666F6F";
setContents(dicts);
r.readDictionary();
}
private void setContents(String hex) {
ByteArrayInputStream in = new ByteArrayInputStream(
StringUtils.fromHexString(hex));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT);
}
}

View File

@@ -0,0 +1,241 @@
package org.briarproject.bramble.data;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals;
public class BdfWriterImplTest extends BrambleTestCase {
private ByteArrayOutputStream out = null;
private BdfWriterImpl w = null;
@Before
public void setUp() {
out = new ByteArrayOutputStream();
w = new BdfWriterImpl(out);
}
@Test
public void testWriteNull() throws IOException {
w.writeNull();
checkContents("00");
}
@Test
public void testWriteBoolean() throws IOException {
w.writeBoolean(true);
w.writeBoolean(false);
// TRUE tag, FALSE tag
checkContents("11" + "10");
}
@Test
public void testWriteLong() throws IOException {
w.writeLong(0);
w.writeLong(-1);
w.writeLong(Byte.MAX_VALUE);
w.writeLong(Byte.MIN_VALUE);
w.writeLong(Short.MAX_VALUE);
w.writeLong(Short.MIN_VALUE);
w.writeLong(Integer.MAX_VALUE);
w.writeLong(Integer.MIN_VALUE);
w.writeLong(Long.MAX_VALUE);
w.writeLong(Long.MIN_VALUE);
// INTEGER_8 tag, 0, INTEGER_8 tag, -1, etc
checkContents("21" + "00" + "21" + "FF" +
"21" + "7F" + "21" + "80" +
"22" + "7FFF" + "22" + "8000" +
"24" + "7FFFFFFF" + "24" + "80000000" +
"28" + "7FFFFFFFFFFFFFFF" + "28" + "8000000000000000");
}
@Test
public void testWriteDouble() throws IOException {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html
// 1 bit for sign, 11 for exponent, 52 for significand
w.writeDouble(0.0); // 0 0 0 -> 0x0000000000000000
w.writeDouble(1.0); // 0 1023 1 -> 0x3FF0000000000000
w.writeDouble(2.0); // 0 1024 1 -> 0x4000000000000000
w.writeDouble(-1.0); // 1 1023 1 -> 0xBFF0000000000000
w.writeDouble(-0.0); // 1 0 0 -> 0x8000000000000000
w.writeDouble(Double.NEGATIVE_INFINITY); // 1 2047 0 -> 0xFFF00000...
w.writeDouble(Double.POSITIVE_INFINITY); // 0 2047 0 -> 0x7FF00000...
w.writeDouble(Double.NaN); // 0 2047 1 -> 0x7FF8000000000000
checkContents("38" + "0000000000000000" + "38" + "3FF0000000000000"
+ "38" + "4000000000000000" + "38" + "BFF0000000000000"
+ "38" + "8000000000000000" + "38" + "FFF0000000000000"
+ "38" + "7FF0000000000000" + "38" + "7FF8000000000000");
}
@Test
public void testWriteString8() throws IOException {
String longest = TestUtils.getRandomString(Byte.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
w.writeString("foo bar baz bam ");
w.writeString(longest);
// STRING_8 tag, length 16, UTF-8 bytes, STRING_8 tag, length 127,
// UTF-8 bytes
checkContents("41" + "10" + "666F6F206261722062617A2062616D20" +
"41" + "7F" + longHex);
}
@Test
public void testWriteString16() throws IOException {
String shortest = TestUtils.getRandomString(Byte.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
String longest = TestUtils.getRandomString(Short.MAX_VALUE);
String longHex = StringUtils.toHexString(longest.getBytes("UTF-8"));
w.writeString(shortest);
w.writeString(longest);
// STRING_16 tag, length 128, UTF-8 bytes, STRING_16 tag,
// length 2^15 - 1, UTF-8 bytes
checkContents("42" + "0080" + shortHex + "42" + "7FFF" + longHex);
}
@Test
public void testWriteString32() throws IOException {
String shortest = TestUtils.getRandomString(Short.MAX_VALUE + 1);
String shortHex = StringUtils.toHexString(shortest.getBytes("UTF-8"));
w.writeString(shortest);
// STRING_32 tag, length 2^15, UTF-8 bytes
checkContents("44" + "00008000" + shortHex);
}
@Test
public void testWriteUtf8String() throws IOException {
String unicode = "\uFDD0\uFDD1\uFDD2\uFDD3";
String hex = StringUtils.toHexString(unicode.getBytes("UTF-8"));
w.writeString(unicode);
// STRING_8 tag, length 12, UTF-8 bytes
checkContents("41" + "0C" + hex);
}
@Test
public void testWriteRaw8() throws IOException {
byte[] longest = new byte[Byte.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
w.writeRaw(new byte[] {1, 2, 3});
w.writeRaw(longest);
// RAW_8 tag, length 3, bytes, RAW_8 tag, length 127, bytes
checkContents("51" + "03" + "010203" + "51" + "7F" + longHex);
}
@Test
public void testWriteRaw16() throws IOException {
byte[] shortest = new byte[Byte.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
byte[] longest = new byte[Short.MAX_VALUE];
String longHex = StringUtils.toHexString(longest);
w.writeRaw(shortest);
w.writeRaw(longest);
// RAW_16 tag, length 128, bytes, RAW_16 tag, length 2^15 - 1, bytes
checkContents("52" + "0080" + shortHex + "52" + "7FFF" + longHex);
}
@Test
public void testWriteRaw32() throws IOException {
byte[] shortest = new byte[Short.MAX_VALUE + 1];
String shortHex = StringUtils.toHexString(shortest);
w.writeRaw(shortest);
// RAW_32 tag, length 2^15, bytes
checkContents("54" + "00008000" + shortHex);
}
@Test
public void testWriteList() throws IOException {
List<Object> l = new ArrayList<Object>();
for (int i = 0; i < 3; i++) l.add(i);
w.writeList(l);
// LIST tag, elements as integers, END tag
checkContents("60" + "21" + "00" + "21" + "01" + "21" + "02" + "80");
}
@Test
public void testListCanContainNull() throws IOException {
List<Object> l = new ArrayList<Object>();
l.add(1);
l.add(null);
l.add(NULL_VALUE);
l.add(2);
w.writeList(l);
// LIST tag, 1 as integer, NULL tag, NULL tag, 2 as integer, END tag
checkContents("60" + "21" + "01" + "00" + "00" + "21" + "02" + "80");
}
@Test
public void testWriteDictionary() throws IOException {
// Use LinkedHashMap to get predictable iteration order
Map<String, Object> m = new LinkedHashMap<String, Object>();
for (int i = 0; i < 4; i++) m.put(String.valueOf(i), i);
w.writeDictionary(m);
// DICTIONARY tag, keys as strings and values as integers, END tag
checkContents("70" + "41" + "01" + "30" + "21" + "00" +
"41" + "01" + "31" + "21" + "01" +
"41" + "01" + "32" + "21" + "02" +
"41" + "01" + "33" + "21" + "03" + "80");
}
@Test
public void testWriteDelimitedList() throws IOException {
w.writeListStart();
w.writeLong(1);
w.writeString("foo");
w.writeLong(128);
w.writeListEnd();
// LIST tag, 1 as integer, "foo" as string, 128 as integer, END tag
checkContents("60" + "21" + "01" +
"41" + "03" + "666F6F" +
"22" + "0080" + "80");
}
@Test
public void testWriteDelimitedDictionary() throws IOException {
w.writeDictionaryStart();
w.writeString("foo");
w.writeLong(123);
w.writeString("bar");
w.writeNull();
w.writeDictionaryEnd();
// DICTIONARY tag, "foo" as string, 123 as integer, "bar" as string,
// NULL tag, END tag
checkContents("70" + "41" + "03" + "666F6F" +
"21" + "7B" + "41" + "03" + "626172" + "00" + "80");
}
@Test
public void testWriteNestedDictionariesAndLists() throws IOException {
Map<String, Object> inner = new LinkedHashMap<String, Object>();
inner.put("bar", new byte[0]);
List<Object> list = new ArrayList<Object>();
list.add(1);
list.add(inner);
Map<String, Object> outer = new LinkedHashMap<String, Object>();
outer.put("foo", list);
w.writeDictionary(outer);
// DICTIONARY tag, "foo" as string, LIST tag, 1 as integer,
// DICTIONARY tag, "bar" as string, {} as raw, END tag, END tag, END tag
checkContents("70" + "41" + "03" + "666F6F" + "60" +
"21" + "01" + "70" + "41" + "03" + "626172" + "51" + "00" +
"80" + "80" + "80");
}
private void checkContents(String hex) throws IOException {
out.flush();
out.close();
byte[] expected = StringUtils.fromHexString(hex);
assertArrayEquals(StringUtils.toHexString(out.toByteArray()),
expected, out.toByteArray());
}
}

View File

@@ -0,0 +1,166 @@
package org.briarproject.bramble.data;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.db.Metadata;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
private MetadataEncoderImpl e;
private MetadataParserImpl p;
private BdfDictionary d;
@Before
public void before() {
e = new MetadataEncoderImpl(new BdfWriterFactoryImpl());
p = new MetadataParserImpl(new BdfReaderFactoryImpl());
d = new BdfDictionary();
}
@Test
public void testBoolean() throws FormatException {
d.put("test", true);
Metadata metadata = e.encode(d);
assertEquals(true, p.parse(metadata).getBoolean("test", false));
}
@Test
public void testInteger() throws FormatException {
d.put("test", 1337);
Metadata metadata = e.encode(d);
assertEquals(1337L, (long) p.parse(metadata).getLong("test", 0L));
}
@Test
public void testLong() throws FormatException {
d.put("test", Long.MAX_VALUE);
Metadata metadata = e.encode(d);
assertEquals(Long.MAX_VALUE,
(long) p.parse(metadata).getLong("test", 0L));
}
@Test
public void testDouble() throws FormatException {
d.put("test", Double.MAX_VALUE);
Metadata metadata = e.encode(d);
assertEquals(Double.MAX_VALUE,
p.parse(metadata).getDouble("test", 0.0), 0);
}
@Test
public void testFloat() throws FormatException {
d.put("test", Float.MIN_NORMAL);
Metadata metadata = e.encode(d);
assertEquals(Float.MIN_NORMAL,
p.parse(metadata).getDouble("test", 0.0), 0);
}
@Test
public void testString() throws FormatException {
d.put("test", "abc");
Metadata metadata = e.encode(d);
assertEquals("abc", p.parse(metadata).getString("test", null));
}
@Test
public void testUtf8String() throws FormatException {
d.put("test", "abcdefghilkmnopqrst \uFDD0\uFDD1\uFDD2\uFDD3");
Metadata metadata = e.encode(d);
assertEquals("abcdefghilkmnopqrst \uFDD0\uFDD1\uFDD2\uFDD3",
p.parse(metadata).getString("test", null));
}
@Test
public void testRaw() throws FormatException {
byte[] b = "\uFDD0\uFDD1\uFDD2\uFDD3".getBytes();
d.put("test", b);
Metadata metadata = e.encode(d);
assertArrayEquals(b, p.parse(metadata).getRaw("test", null));
}
@Test
public void testList() throws FormatException {
List<Long> l = new ArrayList<Long>(4);
l.add(42L);
l.add(1337L);
l.add(Long.MIN_VALUE);
l.add(Long.MAX_VALUE);
d.put("test", l);
Metadata metadata = e.encode(d);
assertArrayEquals(l.toArray(),
p.parse(metadata).getList("test", null).toArray());
}
@Test
public void testDictionary() throws FormatException {
Map<String, Boolean> m = new HashMap<String, Boolean>();
m.put("1", true);
m.put("2", false);
d.put("test", m);
Metadata metadata = e.encode(d);
assertEquals(true, p.parse(metadata).getDictionary("test", null)
.getBoolean("1", false));
assertEquals(false, p.parse(metadata).getDictionary("test", null)
.getBoolean("2", true));
}
@Test
public void testComplexDictionary() throws FormatException {
Map<String, List> m = new HashMap<String, List>();
List<String> one = new ArrayList<String>(3);
one.add("\uFDD0");
one.add("\uFDD1");
one.add("\uFDD2");
m.put("One", one);
List<String> two = new ArrayList<String>(2);
two.add("\u0080");
two.add("\uD800\uDC00");
m.put("Two", two);
d.put("test", m);
Map<String, Boolean> m2 = new HashMap<String, Boolean>();
m2.put("should be true", true);
d.put("another test", m2);
Metadata metadata = e.encode(d);
assertEquals("\uFDD0", p.parse(metadata).getDictionary("test", null)
.getList("One", null).get(0));
assertEquals("\uFDD1", p.parse(metadata).getDictionary("test", null)
.getList("One", null).get(1));
assertEquals("\uFDD2", p.parse(metadata).getDictionary("test", null)
.getList("One", null).get(2));
assertEquals("\u0080", p.parse(metadata).getDictionary("test", null)
.getList("Two", null).get(0));
assertEquals("\uD800\uDC00",
p.parse(metadata).getDictionary("test", null)
.getList("Two", null).get(1));
assertEquals(true, p.parse(metadata).getDictionary("another test", null)
.getBoolean("should be true", false));
}
}

View File

@@ -0,0 +1,344 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import static java.sql.Types.BINARY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class BasicH2Test extends BrambleTestCase {
private static final String CREATE_TABLE =
"CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)";
private static final int BATCH_SIZE = 100;
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
private final String url = "jdbc:h2:" + db.getAbsolutePath();
private Connection connection = null;
@Before
public void setUp() throws Exception {
testDir.mkdirs();
Class.forName("org.h2.Driver");
connection = DriverManager.getConnection(url);
}
@Test
public void testInsertUpdateAndDelete() throws Exception {
// Create the table
createTable(connection);
// Generate an ID and two names
byte[] id = TestUtils.getRandomId();
String oldName = TestUtils.getRandomString(50);
String newName = TestUtils.getRandomString(50);
// Insert the ID and old name into the table
insertRow(id, oldName);
// Check that the old name can be retrieved using the ID
assertTrue(rowExists(id));
assertEquals(oldName, getName(id));
// Update the name
updateRow(id, newName);
// Check that the new name can be retrieved using the ID
assertTrue(rowExists(id));
assertEquals(newName, getName(id));
// Delete the row from the table
assertTrue(deleteRow(id));
// Check that the row no longer exists
assertFalse(rowExists(id));
// Deleting the row again should have no effect
assertFalse(deleteRow(id));
}
@Test
public void testBatchInsertUpdateAndDelete() throws Exception {
// Create the table
createTable(connection);
// Generate some IDs and two sets of names
byte[][] ids = new byte[BATCH_SIZE][];
String[] oldNames = new String[BATCH_SIZE];
String[] newNames = new String[BATCH_SIZE];
for (int i = 0; i < BATCH_SIZE; i++) {
ids[i] = TestUtils.getRandomId();
oldNames[i] = TestUtils.getRandomString(50);
newNames[i] = TestUtils.getRandomString(50);
}
// Insert the IDs and old names into the table as a batch
insertBatch(ids, oldNames);
// Update the names as a batch
updateBatch(ids, newNames);
// Check that the new names can be retrieved using the IDs
for (int i = 0; i < BATCH_SIZE; i++) {
assertTrue(rowExists(ids[i]));
assertEquals(newNames[i], getName(ids[i]));
}
// Delete the rows as a batch
boolean[] deleted = deleteBatch(ids);
// Check that the rows no longer exist
for (int i = 0; i < BATCH_SIZE; i++) {
assertTrue(deleted[i]);
assertFalse(rowExists(ids[i]));
}
// Deleting the rows again should have no effect
deleted = deleteBatch(ids);
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
}
@Test
public void testSortOrder() throws Exception {
byte[] first = new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
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, 127
};
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, (byte) 255
};
// Create the table
createTable(connection);
// Insert the rows
insertRow(first, "first");
insertRow(second, "second");
insertRow(third, "third");
insertRow(null, "null");
// Check the ordering of the < operator: the null ID is not comparable
assertNull(getPredecessor(first));
assertEquals("first", getPredecessor(second));
assertEquals("second", getPredecessor(third));
assertNull(getPredecessor(null));
// Check the ordering of ORDER BY: nulls come first
List<String> names = getNames();
assertEquals(4, names.size());
assertEquals("null", names.get(0));
assertEquals("first", names.get(1));
assertEquals("second", names.get(2));
assertEquals("third", names.get(3));
}
private void createTable(Connection connection) throws SQLException {
try {
Statement s = connection.createStatement();
s.executeUpdate(CREATE_TABLE);
s.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void insertRow(byte[] id, String name) throws SQLException {
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
ps.setString(2, name);
int affected = ps.executeUpdate();
assertEquals(1, affected);
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private boolean rowExists(byte[] id) throws SQLException {
assertNotNull(id);
String sql = "SELECT NULL FROM foo WHERE uniqueID = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
boolean found = rs.next();
assertFalse(rs.next());
rs.close();
ps.close();
return found;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private String getName(byte[] id) throws SQLException {
assertNotNull(id);
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
String name = rs.getString(1);
assertFalse(rs.next());
rs.close();
ps.close();
return name;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void updateRow(byte[] id, String name) throws SQLException {
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(2, BINARY);
else ps.setBytes(2, id);
ps.setString(1, name);
assertEquals(1, ps.executeUpdate());
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private boolean deleteRow(byte[] id) throws SQLException {
String sql = "DELETE FROM foo WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
int affected = ps.executeUpdate();
ps.close();
return affected == 1;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void insertBatch(byte[][] ids, String[] names) throws SQLException {
assertEquals(ids.length, names.length);
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
try {
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < ids.length; i++) {
if (ids[i] == null) ps.setNull(1, BINARY);
else ps.setBytes(1, ids[i]);
ps.setString(2, names[i]);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
for (int affected : batchAffected) assertEquals(1, affected);
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void updateBatch(byte[][] ids, String[] names) throws SQLException {
assertEquals(ids.length, names.length);
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < ids.length; i++) {
if (ids[i] == null) ps.setNull(2, BINARY);
else ps.setBytes(2, ids[i]);
ps.setString(1, names[i]);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
for (int affected : batchAffected) assertEquals(1, affected);
ps.close();
} catch (SQLException e) {
connection.close();
throw e;
}
}
private boolean[] deleteBatch(byte[][] ids) throws SQLException {
String sql = "DELETE FROM foo WHERE uniqueId = ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
for (byte[] id : ids) {
if (id == null) ps.setNull(1, BINARY);
else ps.setBytes(1, id);
ps.addBatch();
}
int[] batchAffected = ps.executeBatch();
assertEquals(ids.length, batchAffected.length);
boolean[] ret = new boolean[ids.length];
for (int i = 0; i < batchAffected.length; i++)
ret[i] = batchAffected[i] == 1;
ps.close();
return ret;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private String getPredecessor(byte[] id) throws SQLException {
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
+ " ORDER BY uniqueId DESC LIMIT ?";
try {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setBytes(1, id);
ps.setInt(2, 1);
ResultSet rs = ps.executeQuery();
String name = rs.next() ? rs.getString(1) : null;
assertFalse(rs.next());
rs.close();
ps.close();
return name;
} catch (SQLException e) {
connection.close();
throw e;
}
}
private List<String> getNames() throws SQLException {
String sql = "SELECT name FROM foo ORDER BY uniqueId";
List<String> names = new ArrayList<String>();
try {
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next()) names.add(rs.getString(1));
rs.close();
ps.close();
return names;
} catch (SQLException e) {
connection.close();
throw e;
}
}
@After
public void tearDown() throws Exception {
if (connection != null) connection.close();
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ExponentialBackoffTest extends BrambleTestCase {
private static final int ONE_HOUR = 60 * 60 * 1000;
@Test
public void testFirstIntervalEqualsRoundTripTime() {
long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
assertEquals(2 * ONE_HOUR, first);
}
@Test
public void testIntervalsIncreaseExponentially() {
long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
long second = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 1);
long third = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 2);
long fourth = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 3);
assertEquals(third, fourth / 2);
assertEquals(second, third / 2);
assertEquals(first, second / 2);
}
@Test
public void testIntervalIsAddedToCurrentTime() {
long now = System.currentTimeMillis();
long fromZero = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
long fromNow = ExponentialBackoff.calculateExpiry(now, ONE_HOUR, 0);
assertEquals(now, fromNow - fromZero);
}
@Test
public void testTransmissionCountOverflow() {
int maxLatency = Integer.MAX_VALUE; // RTT will not overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
assertEquals(Integer.MAX_VALUE * 2L, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 31);
assertEquals(Integer.MAX_VALUE * (2L << 31), expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 32);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 33);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
}
@Test
public void testCurrentTimeOverflow() {
int maxLatency = Integer.MAX_VALUE; // RTT will not overflow
long now = Long.MAX_VALUE - (Integer.MAX_VALUE * (2L << 31));
long expiry = ExponentialBackoff.calculateExpiry(now, maxLatency, 0);
assertEquals(now + Integer.MAX_VALUE * 2L, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(now - 1, maxLatency, 31);
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(now, maxLatency, 31);
assertEquals(Long.MAX_VALUE, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(now + 1, maxLatency, 32);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class LockFairnessTest extends BrambleTestCase {
@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,277 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TransactionIsolationTest extends BrambleTestCase {
private static final String DROP_TABLE = "DROP TABLE foo IF EXISTS";
private static final String CREATE_TABLE = "CREATE TABLE foo"
+ " (key INT NOT NULL,"
+ " counter INT NOT NULL)";
private static final String INSERT_ROW =
"INSERT INTO foo (key, counter) VALUES (1, 123)";
private static final String GET_COUNTER =
"SELECT counter FROM foo WHERE key = 1";
private static final String SET_COUNTER =
"UPDATE foo SET counter = ? WHERE key = 1";
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
private final String withMvcc = "jdbc:h2:" + db.getAbsolutePath()
+ ";MV_STORE=TRUE;MVCC=TRUE";
private final String withoutMvcc = "jdbc:h2:" + db.getAbsolutePath()
+ ";MV_STORE=FALSE;MVCC=FALSE;LOCK_MODE=1";
@Before
public void setUp() throws Exception {
assertTrue(testDir.mkdirs());
Class.forName("org.h2.Driver");
}
@After
public void tearDown() throws Exception {
TestUtils.deleteTestDirectory(testDir);
}
@Test
public void testDoesNotReadUncommittedWritesWithMvcc() throws Exception {
Connection connection = openConnection(true);
try {
createTableAndInsertRow(connection);
} finally {
connection.close();
}
// Start the first transaction
Connection txn1 = openConnection(true);
try {
txn1.setAutoCommit(false);
// The first transaction should read the initial value
assertEquals(123, getCounter(txn1));
// The first transaction updates the value but doesn't commit it
assertEquals(1, setCounter(txn1, 234));
// Start the second transaction
Connection txn2 = openConnection(true);
try {
txn2.setAutoCommit(false);
// The second transaction should still read the initial value
assertEquals(123, getCounter(txn2));
// Commit the second transaction
txn2.commit();
} finally {
txn2.close();
}
// Commit the first transaction
txn1.commit();
} finally {
txn1.close();
}
}
@Test
public void testLastWriterWinsWithMvcc() throws Exception {
Connection connection = openConnection(true);
try {
createTableAndInsertRow(connection);
} finally {
connection.close();
}
// Start the first transaction
Connection txn1 = openConnection(true);
try {
txn1.setAutoCommit(false);
// The first transaction should read the initial value
assertEquals(123, getCounter(txn1));
// The first transaction updates the value but doesn't commit it
assertEquals(1, setCounter(txn1, 234));
// Start the second transaction
Connection txn2 = openConnection(true);
try {
txn2.setAutoCommit(false);
// The second transaction should still read the initial value
assertEquals(123, getCounter(txn2));
// Commit the first transaction
txn1.commit();
// The second transaction updates the value
assertEquals(1, setCounter(txn2, 345));
// Commit the second transaction
txn2.commit();
} finally {
txn2.close();
}
} finally {
txn1.close();
}
// The second transaction was the last writer, so it should win
connection = openConnection(true);
try {
assertEquals(345, getCounter(connection));
} finally {
connection.close();
}
}
@Test
public void testLockTimeoutOnRowWithMvcc() throws Exception {
Connection connection = openConnection(true);
try {
createTableAndInsertRow(connection);
} finally {
connection.close();
}
// Start the first transaction
Connection txn1 = openConnection(true);
try {
txn1.setAutoCommit(false);
// The first transaction should read the initial value
assertEquals(123, getCounter(txn1));
// Start the second transaction
Connection txn2 = openConnection(true);
try {
txn2.setAutoCommit(false);
// The second transaction should read the initial value
assertEquals(123, getCounter(txn2));
// The first transaction updates the value but doesn't commit it
assertEquals(1, setCounter(txn1, 234));
// The second transaction tries to update the value
try {
setCounter(txn2, 345);
fail();
} catch (SQLException expected) {
// Expected: the row is locked by the first transaction
}
// Abort the transactions
txn1.rollback();
txn2.rollback();
} finally {
txn2.close();
}
} finally {
txn1.close();
}
}
@Test
public void testReadLockTimeoutOnTableWithoutMvcc() throws Exception {
Connection connection = openConnection(false);
try {
createTableAndInsertRow(connection);
} finally {
connection.close();
}
// Start the first transaction
Connection txn1 = openConnection(false);
try {
txn1.setAutoCommit(false);
// The first transaction should read the initial value
assertEquals(123, getCounter(txn1));
// Start the second transaction
Connection txn2 = openConnection(false);
try {
txn2.setAutoCommit(false);
// The second transaction should read the initial value
assertEquals(123, getCounter(txn2));
// The first transaction tries to update the value
try {
setCounter(txn1, 345);
fail();
} catch (SQLException expected) {
// Expected: the table is locked by the second transaction
}
// Abort the transactions
txn1.rollback();
txn2.rollback();
} finally {
txn2.close();
}
} finally {
txn1.close();
}
}
@Test
public void testWriteLockTimeoutOnTableWithoutMvcc() throws Exception {
Connection connection = openConnection(false);
try {
createTableAndInsertRow(connection);
} finally {
connection.close();
}
// Start the first transaction
Connection txn1 = openConnection(false);
try {
txn1.setAutoCommit(false);
// The first transaction should read the initial value
assertEquals(123, getCounter(txn1));
// The first transaction updates the value but doesn't commit yet
assertEquals(1, setCounter(txn1, 345));
// Start the second transaction
Connection txn2 = openConnection(false);
try {
txn2.setAutoCommit(false);
// The second transaction tries to read the value
try {
getCounter(txn2);
fail();
} catch (SQLException expected) {
// Expected: the table is locked by the first transaction
}
// Abort the transactions
txn1.rollback();
txn2.rollback();
} finally {
txn2.close();
}
} finally {
txn1.close();
}
}
private Connection openConnection(boolean mvcc) throws SQLException {
return DriverManager.getConnection(mvcc ? withMvcc : withoutMvcc);
}
private void createTableAndInsertRow(Connection c) throws SQLException {
Statement s = c.createStatement();
s.executeUpdate(DROP_TABLE);
s.executeUpdate(CREATE_TABLE);
s.executeUpdate(INSERT_ROW);
s.close();
}
private int getCounter(Connection c) throws SQLException {
Statement s = c.createStatement();
ResultSet rs = s.executeQuery(GET_COUNTER);
assertTrue(rs.next());
int counter = rs.getInt(1);
assertFalse(rs.next());
rs.close();
s.close();
return counter;
}
private int setCounter(Connection c, int counter)
throws SQLException {
PreparedStatement ps = c.prepareStatement(SET_COUNTER);
ps.setInt(1, counter);
int rowsAffected = ps.executeUpdate();
ps.close();
return rowsAffected;
}
}

View File

@@ -0,0 +1,138 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.BrambleMockTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.junit.Assert.assertEquals;
public class IdentityManagerImplTest extends BrambleMockTestCase {
private final IdentityManager identityManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final Transaction txn = new Transaction(null, false);
private final LocalAuthor localAuthor =
new LocalAuthor(new AuthorId(TestUtils.getRandomId()),
TestUtils.getRandomString(8), TestUtils.getRandomBytes(42),
TestUtils.getRandomBytes(42), 0);
private final Collection<LocalAuthor> localAuthors =
Collections.singletonList(localAuthor);
public IdentityManagerImplTest() {
identityManager = new IdentityManagerImpl(db);
}
@Test
public void testRegisterLocalAuthor() throws DbException {
expectRegisterLocalAuthor();
identityManager.registerLocalAuthor(localAuthor);
}
private void expectRegisterLocalAuthor() throws DbException {
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).addLocalAuthor(txn, localAuthor);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
}
@Test
public void testGetLocalAuthor() throws DbException {
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getLocalAuthors(txn);
will(returnValue(localAuthors));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(localAuthor, identityManager.getLocalAuthor());
}
@Test
public void testGetCachedLocalAuthor() throws DbException {
expectRegisterLocalAuthor();
identityManager.registerLocalAuthor(localAuthor);
assertEquals(localAuthor, identityManager.getLocalAuthor());
}
@Test
public void testGetAuthorStatus() throws DbException {
final AuthorId authorId = new AuthorId(TestUtils.getRandomId());
final Collection<Contact> contacts = new ArrayList<Contact>();
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(db).getLocalAuthors(txn);
will(returnValue(localAuthors));
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
oneOf(db).endTransaction(txn);
}});
assertEquals(UNKNOWN, identityManager.getAuthorStatus(authorId));
// add one unverified contact
Author author = new Author(authorId, TestUtils.getRandomString(8),
TestUtils.getRandomBytes(42));
Contact contact =
new Contact(new ContactId(1), author, localAuthor.getId(),
false, true);
contacts.add(contact);
checkAuthorStatusContext(authorId, contacts);
assertEquals(UNVERIFIED, identityManager.getAuthorStatus(authorId));
// add one verified contact
Contact contact2 =
new Contact(new ContactId(1), author, localAuthor.getId(),
true, true);
contacts.add(contact2);
checkAuthorStatusContext(authorId, contacts);
assertEquals(VERIFIED, identityManager.getAuthorStatus(authorId));
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
never(db).getLocalAuthors(txn);
never(db).getContactsByAuthorId(txn, authorId);
oneOf(db).endTransaction(txn);
}});
assertEquals(OURSELVES,
identityManager.getAuthorStatus(localAuthor.getId()));
}
private void checkAuthorStatusContext(final AuthorId authorId,
final Collection<Contact> contacts) throws DbException {
context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
never(db).getLocalAuthors(txn);
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
oneOf(db).endTransaction(txn);
}});
}
}

View File

@@ -0,0 +1,393 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Rule;
import org.junit.Test;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class KeyAgreementProtocolTest extends BrambleTestCase {
@Rule
public JUnitRuleMockery context = new JUnitRuleMockery() {{
// So we can mock concrete classes like KeyAgreementTransport
setImposteriser(ClassImposteriser.INSTANCE);
}};
private static final byte[] ALICE_PUBKEY = TestUtils.getRandomBytes(32);
private static final byte[] ALICE_COMMIT =
TestUtils.getRandomBytes(COMMIT_LENGTH);
private static final byte[] ALICE_PAYLOAD =
TestUtils.getRandomBytes(COMMIT_LENGTH + 8);
private static final byte[] BOB_PUBKEY = TestUtils.getRandomBytes(32);
private static final byte[] BOB_COMMIT =
TestUtils.getRandomBytes(COMMIT_LENGTH);
private static final byte[] BOB_PAYLOAD =
TestUtils.getRandomBytes(COMMIT_LENGTH + 19);
private static final byte[] ALICE_CONFIRM =
TestUtils.getRandomBytes(SecretKey.LENGTH);
private static final byte[] BOB_CONFIRM =
TestUtils.getRandomBytes(SecretKey.LENGTH);
private static final byte[] BAD_PUBKEY = TestUtils.getRandomBytes(32);
private static final byte[] BAD_COMMIT =
TestUtils.getRandomBytes(COMMIT_LENGTH);
private static final byte[] BAD_CONFIRM =
TestUtils.getRandomBytes(SecretKey.LENGTH);
@Mock
KeyAgreementProtocol.Callbacks callbacks;
@Mock
CryptoComponent crypto;
@Mock
PayloadEncoder payloadEncoder;
@Mock
KeyAgreementTransport transport;
@Mock
PublicKey ourPubKey;
@Test
public void testAliceProtocol() throws Exception {
// set up
final Payload theirPayload = new Payload(BOB_COMMIT, null);
final Payload ourPayload = new Payload(ALICE_COMMIT, null);
final KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
final SecretKey sharedSecret = TestUtils.getSecretKey();
final SecretKey masterSecret = TestUtils.getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, true);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(ALICE_PAYLOAD));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(BOB_PAYLOAD));
allowing(ourPubKey).getEncoded();
will(returnValue(ALICE_PUBKEY));
// Alice sends her public key
oneOf(transport).sendKey(ALICE_PUBKEY);
// Alice receives Bob's public key
oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey();
will(returnValue(BOB_PUBKEY));
oneOf(callbacks).initialPacketReceived();
// Alice verifies Bob's public key
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
will(returnValue(BOB_COMMIT));
// Alice computes shared secret
oneOf(crypto).deriveSharedSecret(BOB_PUBKEY, ourKeyPair, true);
will(returnValue(sharedSecret));
// Alice sends her confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, true);
will(returnValue(ALICE_CONFIRM));
oneOf(transport).sendConfirm(ALICE_CONFIRM);
// Alice receives Bob's confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(BOB_CONFIRM));
// Alice verifies Bob's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, false);
will(returnValue(BOB_CONFIRM));
// Alice computes master secret
oneOf(crypto).deriveMasterSecret(sharedSecret);
will(returnValue(masterSecret));
}});
// execute
assertThat(masterSecret, is(equalTo(protocol.perform())));
}
@Test
public void testBobProtocol() throws Exception {
// set up
final Payload theirPayload = new Payload(ALICE_COMMIT, null);
final Payload ourPayload = new Payload(BOB_COMMIT, null);
final KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
final SecretKey sharedSecret = TestUtils.getSecretKey();
final SecretKey masterSecret = TestUtils.getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, false);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(BOB_PAYLOAD));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(ALICE_PAYLOAD));
allowing(ourPubKey).getEncoded();
will(returnValue(BOB_PUBKEY));
// Bob receives Alice's public key
oneOf(transport).receiveKey();
will(returnValue(ALICE_PUBKEY));
oneOf(callbacks).initialPacketReceived();
// Bob verifies Alice's public key
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
will(returnValue(ALICE_COMMIT));
// Bob sends his public key
oneOf(transport).sendKey(BOB_PUBKEY);
// Bob computes shared secret
oneOf(crypto).deriveSharedSecret(ALICE_PUBKEY, ourKeyPair, false);
will(returnValue(sharedSecret));
// Bob receives Alices's confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(ALICE_CONFIRM));
// Bob verifies Alice's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, true);
will(returnValue(ALICE_CONFIRM));
// Bob sends his confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, false);
will(returnValue(BOB_CONFIRM));
oneOf(transport).sendConfirm(BOB_CONFIRM);
// Bob computes master secret
oneOf(crypto).deriveMasterSecret(sharedSecret);
will(returnValue(masterSecret));
}});
// execute
assertThat(masterSecret, is(equalTo(protocol.perform())));
}
@Test(expected = AbortException.class)
public void testAliceProtocolAbortOnBadKey() throws Exception {
// set up
final Payload theirPayload = new Payload(BOB_COMMIT, null);
final Payload ourPayload = new Payload(ALICE_COMMIT, null);
final KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, true);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(ourPubKey).getEncoded();
will(returnValue(ALICE_PUBKEY));
// Alice sends her public key
oneOf(transport).sendKey(ALICE_PUBKEY);
// Alice receives a bad public key
oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey();
will(returnValue(BAD_PUBKEY));
oneOf(callbacks).initialPacketReceived();
// Alice verifies Bob's public key
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
will(returnValue(BAD_COMMIT));
// Alice aborts
oneOf(transport).sendAbort(false);
// Alice never computes shared secret
never(crypto).deriveSharedSecret(BAD_PUBKEY, ourKeyPair, true);
}});
// execute
protocol.perform();
}
@Test(expected = AbortException.class)
public void testBobProtocolAbortOnBadKey() throws Exception {
// set up
final Payload theirPayload = new Payload(ALICE_COMMIT, null);
final Payload ourPayload = new Payload(BOB_COMMIT, null);
final KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, false);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(ourPubKey).getEncoded();
will(returnValue(BOB_PUBKEY));
// Bob receives a bad public key
oneOf(transport).receiveKey();
will(returnValue(BAD_PUBKEY));
oneOf(callbacks).initialPacketReceived();
// Bob verifies Alice's public key
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
will(returnValue(BAD_COMMIT));
// Bob aborts
oneOf(transport).sendAbort(false);
// Bob never sends his public key
never(transport).sendKey(BOB_PUBKEY);
}});
// execute
protocol.perform();
}
@Test(expected = AbortException.class)
public void testAliceProtocolAbortOnBadConfirm() throws Exception {
// set up
final Payload theirPayload = new Payload(BOB_COMMIT, null);
final Payload ourPayload = new Payload(ALICE_COMMIT, null);
final KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
final SecretKey sharedSecret = TestUtils.getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, true);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(ALICE_PAYLOAD));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(BOB_PAYLOAD));
allowing(ourPubKey).getEncoded();
will(returnValue(ALICE_PUBKEY));
// Alice sends her public key
oneOf(transport).sendKey(ALICE_PUBKEY);
// Alice receives Bob's public key
oneOf(callbacks).connectionWaiting();
oneOf(transport).receiveKey();
will(returnValue(BOB_PUBKEY));
oneOf(callbacks).initialPacketReceived();
// Alice verifies Bob's public key
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
will(returnValue(BOB_COMMIT));
// Alice computes shared secret
oneOf(crypto).deriveSharedSecret(BOB_PUBKEY, ourKeyPair, true);
will(returnValue(sharedSecret));
// Alice sends her confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, true);
will(returnValue(ALICE_CONFIRM));
oneOf(transport).sendConfirm(ALICE_CONFIRM);
// Alice receives a bad confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(BAD_CONFIRM));
// Alice verifies Bob's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, false);
will(returnValue(BOB_CONFIRM));
// Alice aborts
oneOf(transport).sendAbort(false);
// Alice never computes master secret
never(crypto).deriveMasterSecret(sharedSecret);
}});
// execute
protocol.perform();
}
@Test(expected = AbortException.class)
public void testBobProtocolAbortOnBadConfirm() throws Exception {
// set up
final Payload theirPayload = new Payload(ALICE_COMMIT, null);
final Payload ourPayload = new Payload(BOB_COMMIT, null);
final KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
final SecretKey sharedSecret = TestUtils.getSecretKey();
KeyAgreementProtocol protocol =
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
transport, theirPayload, ourPayload, ourKeyPair, false);
// expectations
context.checking(new Expectations() {{
// Helpers
allowing(payloadEncoder).encode(ourPayload);
will(returnValue(BOB_PAYLOAD));
allowing(payloadEncoder).encode(theirPayload);
will(returnValue(ALICE_PAYLOAD));
allowing(ourPubKey).getEncoded();
will(returnValue(BOB_PUBKEY));
// Bob receives Alice's public key
oneOf(transport).receiveKey();
will(returnValue(ALICE_PUBKEY));
oneOf(callbacks).initialPacketReceived();
// Bob verifies Alice's public key
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
will(returnValue(ALICE_COMMIT));
// Bob sends his public key
oneOf(transport).sendKey(BOB_PUBKEY);
// Bob computes shared secret
oneOf(crypto).deriveSharedSecret(ALICE_PUBKEY, ourKeyPair, false);
will(returnValue(sharedSecret));
// Bob receives a bad confirmation record
oneOf(transport).receiveConfirm();
will(returnValue(BAD_CONFIRM));
// Bob verifies Alice's confirmation record
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, true);
will(returnValue(ALICE_CONFIRM));
// Bob aborts
oneOf(transport).sendAbort(false);
// Bob never sends his confirmation record
never(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, false);
}});
// execute
protocol.perform();
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ShutdownManagerImplTest extends BrambleTestCase {
@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() {
@Override
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,63 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class BackoffImplTest extends BrambleTestCase {
private static final int MIN_INTERVAL = 60 * 1000;
private static final int MAX_INTERVAL = 60 * 60 * 1000;
private static final double BASE = 1.2;
@Test
public void testPollingIntervalStartsAtMinimum() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testIncrementIncreasesPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
assertTrue(b.getPollingInterval() > MIN_INTERVAL);
}
@Test
public void testResetResetsPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
b.increment();
b.reset();
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testBaseAffectsBackoffSpeed() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
int interval = b.getPollingInterval();
BackoffImpl b1 = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE * 2);
b1.increment();
int interval1 = b1.getPollingInterval();
assertTrue(interval < interval1);
}
@Test
public void testIntervalDoesNotExceedMaxInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
for (int i = 0; i < 100; i++) b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}
@Test
public void testIntervalDoesNotExceedMaxIntervalWithInfiniteMultiplier() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL,
Double.POSITIVE_INFINITY);
b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}
}

View File

@@ -0,0 +1,106 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class ConnectionRegistryImplTest extends BrambleTestCase {
private final ContactId contactId, contactId1;
private final TransportId transportId, transportId1;
public ConnectionRegistryImplTest() {
contactId = new ContactId(1);
contactId1 = new ContactId(2);
transportId = new TransportId("id");
transportId1 = new TransportId("id1");
}
@Test
public void testRegisterAndUnregister() {
Mockery context = new Mockery();
final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{
exactly(5).of(eventBus).broadcast(with(any(
ConnectionOpenedEvent.class)));
exactly(2).of(eventBus).broadcast(with(any(
ConnectionClosedEvent.class)));
exactly(3).of(eventBus).broadcast(with(any(
ContactConnectedEvent.class)));
oneOf(eventBus).broadcast(with(any(
ContactDisconnectedEvent.class)));
}});
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus);
// The registry should be empty
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Check that a registered connection shows up - this should
// broadcast a ConnectionOpenedEvent and a ContactConnectedEvent
c.registerConnection(contactId, transportId, true);
assertEquals(Collections.singletonList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Register an identical connection - this should broadcast a
// ConnectionOpenedEvent and lookup should be unaffected
c.registerConnection(contactId, transportId, true);
assertEquals(Collections.singletonList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Unregister one of the connections - this should broadcast a
// ConnectionClosedEvent and lookup should be unaffected
c.unregisterConnection(contactId, transportId, true);
assertEquals(Collections.singletonList(contactId),
c.getConnectedContacts(transportId));
assertEquals(Collections.emptyList(),
c.getConnectedContacts(transportId1));
// Unregister the other connection - this should broadcast a
// ConnectionClosedEvent and a ContactDisconnectedEvent
c.unregisterConnection(contactId, transportId, true);
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, true);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
// Register both contacts with one transport, one contact with both -
// this should broadcast three ConnectionOpenedEvents and two
// ContactConnectedEvents
c.registerConnection(contactId, transportId, true);
c.registerConnection(contactId1, transportId, true);
c.registerConnection(contactId1, transportId1, true);
Collection<ContactId> connected = c.getConnectedContacts(transportId);
assertEquals(2, connected.size());
assertTrue(connected.contains(contactId));
assertTrue(connected.contains(contactId1));
assertEquals(Collections.singletonList(contactId1),
c.getConnectedContacts(transportId1));
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,124 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.ui.UiCallback;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Test;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class PluginManagerImplTest extends BrambleTestCase {
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery() {{
setThreadingPolicy(new Synchroniser());
}};
final Executor ioExecutor = Executors.newSingleThreadExecutor();
final EventBus eventBus = context.mock(EventBus.class);
final PluginConfig pluginConfig = context.mock(PluginConfig.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final SettingsManager settingsManager =
context.mock(SettingsManager.class);
final TransportPropertyManager transportPropertyManager =
context.mock(TransportPropertyManager.class);
final UiCallback uiCallback = context.mock(UiCallback.class);
// Two simplex plugin factories: both create plugins, one fails to start
final SimplexPluginFactory simplexFactory =
context.mock(SimplexPluginFactory.class);
final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
final TransportId simplexId = new TransportId("simplex");
final SimplexPluginFactory simplexFailFactory =
context.mock(SimplexPluginFactory.class, "simplexFailFactory");
final SimplexPlugin simplexFailPlugin =
context.mock(SimplexPlugin.class, "simplexFailPlugin");
final TransportId simplexFailId = new TransportId("simplex1");
// Two duplex plugin factories: one creates a plugin, the other fails
final DuplexPluginFactory duplexFactory =
context.mock(DuplexPluginFactory.class);
final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
final TransportId duplexId = new TransportId("duplex");
final DuplexPluginFactory duplexFailFactory =
context.mock(DuplexPluginFactory.class, "duplexFailFactory");
final TransportId duplexFailId = new TransportId("duplex1");
context.checking(new Expectations() {{
allowing(simplexPlugin).getId();
will(returnValue(simplexId));
allowing(simplexFailPlugin).getId();
will(returnValue(simplexFailId));
allowing(duplexPlugin).getId();
will(returnValue(duplexId));
// start()
// First simplex plugin
oneOf(pluginConfig).getSimplexFactories();
will(returnValue(Arrays.asList(simplexFactory,
simplexFailFactory)));
oneOf(simplexFactory).getId();
will(returnValue(simplexId));
oneOf(simplexFactory).createPlugin(with(any(
SimplexPluginCallback.class)));
will(returnValue(simplexPlugin)); // Created
oneOf(simplexPlugin).start();
will(returnValue(true)); // Started
// Second simplex plugin
oneOf(simplexFailFactory).getId();
will(returnValue(simplexFailId));
oneOf(simplexFailFactory).createPlugin(with(any(
SimplexPluginCallback.class)));
will(returnValue(simplexFailPlugin)); // Created
oneOf(simplexFailPlugin).start();
will(returnValue(false)); // Failed to start
// First duplex plugin
oneOf(pluginConfig).getDuplexFactories();
will(returnValue(Arrays.asList(duplexFactory, duplexFailFactory)));
oneOf(duplexFactory).getId();
will(returnValue(duplexId));
oneOf(duplexFactory).createPlugin(with(any(
DuplexPluginCallback.class)));
will(returnValue(duplexPlugin)); // Created
oneOf(duplexPlugin).start();
will(returnValue(true)); // Started
// Second duplex plugin
oneOf(duplexFailFactory).getId();
will(returnValue(duplexFailId));
oneOf(duplexFailFactory).createPlugin(with(any(
DuplexPluginCallback.class)));
will(returnValue(null)); // Failed to create a plugin
// stop()
// Stop the plugins
oneOf(simplexPlugin).stop();
oneOf(simplexFailPlugin).stop();
oneOf(duplexPlugin).stop();
}});
PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
pluginConfig, connectionManager, settingsManager,
transportPropertyManager, uiCallback);
// Two plugins should be started and stopped
p.startService();
p.stopService();
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,354 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.RunAction;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.ImmediateExecutor;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.system.Clock;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
public class PollerTest extends BrambleTestCase {
private final ContactId contactId = new ContactId(234);
private final int pollingInterval = 60 * 1000;
private final long now = System.currentTimeMillis();
@Test
public void testConnectOnContactStatusChanged() throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
// Two simplex plugins: one supports polling, the other doesn't
final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
final SimplexPlugin simplexPlugin1 =
context.mock(SimplexPlugin.class, "simplexPlugin1");
final TransportId simplexId1 = new TransportId("simplex1");
final List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
simplexPlugin1);
final TransportConnectionWriter simplexWriter =
context.mock(TransportConnectionWriter.class);
// Two duplex plugins: one supports polling, the other doesn't
final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
final TransportId duplexId = new TransportId("duplex");
final DuplexPlugin duplexPlugin1 =
context.mock(DuplexPlugin.class, "duplexPlugin1");
final List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
duplexPlugin1);
final DuplexTransportConnection duplexConnection =
context.mock(DuplexTransportConnection.class);
context.checking(new Expectations() {{
// Get the simplex plugins
oneOf(pluginManager).getSimplexPlugins();
will(returnValue(simplexPlugins));
// The first plugin doesn't support polling
oneOf(simplexPlugin).shouldPoll();
will(returnValue(false));
// The second plugin supports polling
oneOf(simplexPlugin1).shouldPoll();
will(returnValue(true));
// Check whether the contact is already connected
oneOf(simplexPlugin1).getId();
will(returnValue(simplexId1));
oneOf(connectionRegistry).isConnected(contactId, simplexId1);
will(returnValue(false));
// Connect to the contact
oneOf(simplexPlugin1).createWriter(contactId);
will(returnValue(simplexWriter));
// Pass the connection to the connection manager
oneOf(connectionManager).manageOutgoingConnection(contactId,
simplexId1, simplexWriter);
// Get the duplex plugins
oneOf(pluginManager).getDuplexPlugins();
will(returnValue(duplexPlugins));
// The first plugin supports polling
oneOf(duplexPlugin).shouldPoll();
will(returnValue(true));
// Check whether the contact is already connected
oneOf(duplexPlugin).getId();
will(returnValue(duplexId));
oneOf(connectionRegistry).isConnected(contactId, duplexId);
will(returnValue(false));
// Connect to the contact
oneOf(duplexPlugin).createConnection(contactId);
will(returnValue(duplexConnection));
// Pass the connection to the connection manager
oneOf(connectionManager).manageOutgoingConnection(contactId,
duplexId, duplexConnection);
// The second plugin doesn't support polling
oneOf(duplexPlugin1).shouldPoll();
will(returnValue(false));
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
context.assertIsSatisfied();
}
@Test
public void testRescheduleAndReconnectOnConnectionClosed()
throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
final TransportId transportId = new TransportId("id");
final DuplexTransportConnection duplexConnection =
context.mock(DuplexTransportConnection.class);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// reschedule()
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
// connectToContact()
// Check whether the contact is already connected
oneOf(connectionRegistry).isConnected(contactId, transportId);
will(returnValue(false));
// Connect to the contact
oneOf(plugin).createConnection(contactId);
will(returnValue(duplexConnection));
// Pass the connection to the connection manager
oneOf(connectionManager).manageOutgoingConnection(contactId,
transportId, duplexConnection);
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
false));
context.assertIsSatisfied();
}
@Test
public void testRescheduleOnConnectionOpened() throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
final TransportId transportId = new TransportId("id");
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
context.assertIsSatisfied();
}
@Test
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
final TransportId transportId = new TransportId("id");
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// First event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
// Second event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Don't replace the previously scheduled task, due earlier
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now + 1));
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
context.assertIsSatisfied();
}
@Test
public void testPollOnTransportEnabled() throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final Plugin plugin = context.mock(Plugin.class);
final TransportId transportId = new TransportId("id");
final List<ContactId> connected = Collections.singletonList(contactId);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule a polling task immediately
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
with(MILLISECONDS));
will(new RunAction());
// Running the polling task schedules the next polling task
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(random).nextDouble();
will(returnValue(0.5));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
// Poll the plugin
oneOf(connectionRegistry).getConnectedContacts(transportId);
will(returnValue(connected));
oneOf(plugin).poll(connected);
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new TransportEnabledEvent(transportId));
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,372 @@
package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.junit.Test;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class LanTcpPluginTest extends BrambleTestCase {
private final ContactId contactId = new ContactId(234);
private final Backoff backoff = new TestBackoff();
@Test
public void testAddressesAreOnSameLan() {
LanTcpPlugin plugin = new LanTcpPlugin(null, null, null, 0, 0);
// Local and remote in 10.0.0.0/8 should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(10, 255, 255, 255)));
// Local and remote in 172.16.0.0/12 should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(172, 31, 255, 255)));
// Local and remote in 192.168.0.0/16 should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(192, 168, 255, 255)));
// Local and remote in different recognised prefixes should return false
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(172, 31, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(192, 168, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(10, 255, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(192, 168, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(10, 255, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(172, 31, 255, 255)));
// Remote prefix unrecognised should return false
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(1, 2, 3, 4)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(1, 2, 3, 4)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(1, 2, 3, 4)));
// Both prefixes unrecognised should return true (could be link-local)
assertTrue(plugin.addressesAreOnSameLan(makeAddress(1, 2, 3, 4),
makeAddress(5, 6, 7, 8)));
}
private byte[] makeAddress(int... parts) {
byte[] b = new byte[parts.length];
for (int i = 0; i < parts.length; i++) b[i] = (byte) parts[i];
return b;
}
@Test
public void testIncomingConnection() throws Exception {
if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
Executor executor = Executors.newCachedThreadPool();
DuplexPlugin plugin = new LanTcpPlugin(executor, backoff, callback,
0, 0);
plugin.start();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
String ipPorts = callback.local.get("ipPorts");
assertNotNull(ipPorts);
String[] split = ipPorts.split(",");
assertEquals(1, split.length);
split = split[0].split(":");
assertEquals(2, split.length);
String addrString = split[0], portString = split[1];
InetAddress addr = InetAddress.getByName(addrString);
assertTrue(addr instanceof Inet4Address);
assertFalse(addr.isLoopbackAddress());
assertTrue(addr.isLinkLocalAddress() || addr.isSiteLocalAddress());
int port = Integer.parseInt(portString);
assertTrue(port > 0 && port < 65536);
// The plugin should be listening on the port
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);
Socket s = new Socket();
s.connect(socketAddr, 100);
assertTrue(callback.connectionsLatch.await(5, SECONDS));
s.close();
// Stop the plugin
plugin.stop();
}
@Test
public void testOutgoingConnection() throws Exception {
if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
Executor executor = Executors.newCachedThreadPool();
DuplexPlugin plugin = new LanTcpPlugin(executor, backoff, callback,
0, 0);
plugin.start();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
assertTrue(callback.propertiesLatch.await(5, SECONDS));
String ipPorts = callback.local.get("ipPorts");
assertNotNull(ipPorts);
String[] split = ipPorts.split(",");
assertEquals(1, split.length);
split = split[0].split(":");
assertEquals(2, split.length);
String addrString = split[0];
// Listen on the same interface as the plugin
final ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress(addrString, 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("ipPorts", addrString + ":" + 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.getReader().dispose(false, true);
d.getWriter().dispose(false);
ss.close();
plugin.stop();
}
@Test
public void testIncomingKeyAgreementConnection() throws Exception {
if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
Executor executor = Executors.newCachedThreadPool();
DuplexPlugin plugin = new LanTcpPlugin(executor, backoff, callback,
0, 0);
plugin.start();
assertTrue(callback.propertiesLatch.await(5, SECONDS));
KeyAgreementListener kal =
plugin.createKeyAgreementListener(new byte[COMMIT_LENGTH]);
assertNotNull(kal);
Callable<KeyAgreementConnection> c = kal.listen();
FutureTask<KeyAgreementConnection> f =
new FutureTask<KeyAgreementConnection>(c);
new Thread(f).start();
// The plugin should have bound a socket and stored the port number
BdfList descriptor = kal.getDescriptor();
assertEquals(3, descriptor.size());
assertEquals(TRANSPORT_ID_LAN, descriptor.getLong(0).longValue());
byte[] address = descriptor.getRaw(1);
InetAddress addr = InetAddress.getByAddress(address);
assertTrue(addr instanceof Inet4Address);
assertFalse(addr.isLoopbackAddress());
assertTrue(addr.isLinkLocalAddress() || addr.isSiteLocalAddress());
int port = descriptor.getLong(2).intValue();
assertTrue(port > 0 && port < 65536);
// The plugin should be listening on the port
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);
Socket s = new Socket();
s.connect(socketAddr, 100);
assertNotNull(f.get(5, SECONDS));
s.close();
kal.close();
// Stop the plugin
plugin.stop();
}
@Test
public void testOutgoingKeyAgreementConnection() throws Exception {
if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
Executor executor = Executors.newCachedThreadPool();
DuplexPlugin plugin = new LanTcpPlugin(executor, backoff, callback,
0, 0);
plugin.start();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
String ipPorts = callback.local.get("ipPorts");
assertNotNull(ipPorts);
String[] split = ipPorts.split(",");
assertEquals(1, split.length);
split = split[0].split(":");
assertEquals(2, split.length);
String addrString = split[0];
// Listen on the same interface as the plugin
final ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress(addrString, 0), 10);
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
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_LAN);
InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress();
descriptor.add(local.getAddress().getAddress());
descriptor.add(local.getPort());
// Connect to the port
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
new byte[COMMIT_LENGTH], descriptor, 5000);
assertNotNull(d);
// Check that the connection was accepted
assertTrue(latch.await(5, SECONDS));
assertFalse(error.get());
// Clean up
d.getReader().dispose(false, true);
d.getWriter().dispose(false);
ss.close();
plugin.stop();
}
private boolean systemHasLocalIpv4Address() throws Exception {
for (NetworkInterface i : Collections.list(
NetworkInterface.getNetworkInterfaces())) {
for (InetAddress a : Collections.list(i.getInetAddresses())) {
if (a instanceof Inet4Address)
return a.isLinkLocalAddress() || a.isSiteLocalAddress();
}
}
return false;
}
@NotNullByDefault
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();
@Override
public Settings getSettings() {
return new Settings();
}
@Override
public TransportProperties getLocalProperties() {
return local;
}
@Override
public Map<ContactId, TransportProperties> getRemoteProperties() {
return remote;
}
@Override
public void mergeSettings(Settings s) {
}
@Override
public void mergeLocalProperties(TransportProperties p) {
local.putAll(p);
propertiesLatch.countDown();
}
@Override
public int showChoice(String[] options, String... message) {
return -1;
}
@Override
public boolean showConfirmationMessage(String... message) {
return false;
}
@Override
public void showMessage(String... message) {
}
@Override
public void incomingConnectionCreated(DuplexTransportConnection d) {
connectionsLatch.countDown();
}
@Override
public void outgoingConnectionCreated(ContactId c,
DuplexTransportConnection d) {
}
@Override
public void transportEnabled() {
}
@Override
public void transportDisabled() {
}
}
private static class TestBackoff implements Backoff {
@Override
public int getPollingInterval() {
return 60 * 1000;
}
@Override
public void increment() {
}
@Override
public void reset() {
}
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.bramble.properties;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class TransportPropertyManagerImplTest extends BrambleTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.bramble.properties;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.IOException;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static org.junit.Assert.assertEquals;
public class TransportPropertyValidatorTest extends BrambleTestCase {
private final TransportId transportId;
private final BdfDictionary bdfDictionary;
private final Group group;
private final Message message;
private final TransportPropertyValidator tpv;
public TransportPropertyValidatorTest() {
transportId = new TransportId("test");
bdfDictionary = new BdfDictionary();
GroupId groupId = new GroupId(TestUtils.getRandomId());
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
byte[] descriptor = TestUtils.getRandomBytes(12);
group = new Group(groupId, clientId, descriptor);
MessageId messageId = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
byte[] body = TestUtils.getRandomBytes(123);
message = new Message(messageId, groupId, timestamp, body);
Mockery context = new Mockery();
ClientHelper clientHelper = context.mock(ClientHelper.class);
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
Clock clock = context.mock(Clock.class);
tpv = new TransportPropertyValidator(clientHelper, metadataEncoder,
clock);
}
@Test
public void testValidateProperMessage() throws IOException {
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
BdfDictionary result = tpv.validateMessage(message, group, body)
.getDictionary();
assertEquals("test", result.getString("transportId"));
assertEquals(4, result.getLong("version").longValue());
}
@Test(expected = FormatException.class)
public void testValidateWrongVersionValue() throws IOException {
BdfList body = BdfList.of(transportId.getString(), -1, bdfDictionary);
tpv.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateWrongVersionType() throws IOException {
BdfList body = BdfList.of(transportId.getString(), bdfDictionary,
bdfDictionary);
tpv.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateLongTransportId() throws IOException {
String wrongTransportIdString =
TestUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
BdfList body = BdfList.of(wrongTransportIdString, 4, bdfDictionary);
tpv.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateEmptyTransportId() throws IOException {
BdfList body = BdfList.of("", 4, bdfDictionary);
tpv.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testValidateTooManyProperties() throws IOException {
BdfDictionary d = new BdfDictionary();
for (int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT + 1; i++)
d.put(String.valueOf(i), i);
BdfList body = BdfList.of(transportId.getString(), 4, d);
tpv.validateMessage(message, group, body);
}
}

View File

@@ -0,0 +1,162 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
import static org.junit.Assert.assertEquals;
public class PacketReaderImplTest extends BrambleTestCase {
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
byte[] b = createAck(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readAck();
}
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readAck();
}
@Test(expected = FormatException.class)
public void testEmptyAck() throws Exception {
byte[] b = createEmptyAck();
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readAck();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
byte[] b = createOffer(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readOffer();
}
@Test
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
byte[] b = createOffer(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readOffer();
}
@Test(expected = FormatException.class)
public void testEmptyOffer() throws Exception {
byte[] b = createEmptyOffer();
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readOffer();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
byte[] b = createRequest(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readRequest();
}
@Test
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
byte[] b = createRequest(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readRequest();
}
@Test(expected = FormatException.class)
public void testEmptyRequest() throws Exception {
byte[] b = createEmptyRequest();
ByteArrayInputStream in = new ByteArrayInputStream(b);
PacketReaderImpl reader = new PacketReaderImpl(null, in);
reader.readRequest();
}
private byte[] createAck(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[PACKET_HEADER_LENGTH]);
while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
+ MAX_PACKET_PAYLOAD_LENGTH) {
out.write(TestUtils.getRandomId());
}
if (tooBig) out.write(TestUtils.getRandomId());
assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
MAX_PACKET_PAYLOAD_LENGTH);
byte[] packet = out.toByteArray();
packet[1] = ACK;
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
return packet;
}
private byte[] createEmptyAck() throws Exception {
byte[] packet = new byte[PACKET_HEADER_LENGTH];
packet[1] = ACK;
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
return packet;
}
private byte[] createOffer(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[PACKET_HEADER_LENGTH]);
while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
+ MAX_PACKET_PAYLOAD_LENGTH) {
out.write(TestUtils.getRandomId());
}
if (tooBig) out.write(TestUtils.getRandomId());
assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
MAX_PACKET_PAYLOAD_LENGTH);
byte[] packet = out.toByteArray();
packet[1] = OFFER;
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
return packet;
}
private byte[] createEmptyOffer() throws Exception {
byte[] packet = new byte[PACKET_HEADER_LENGTH];
packet[1] = OFFER;
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
return packet;
}
private byte[] createRequest(boolean tooBig) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(new byte[PACKET_HEADER_LENGTH]);
while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
+ MAX_PACKET_PAYLOAD_LENGTH) {
out.write(TestUtils.getRandomId());
}
if (tooBig) out.write(TestUtils.getRandomId());
assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
MAX_PACKET_PAYLOAD_LENGTH);
byte[] packet = out.toByteArray();
packet[1] = REQUEST;
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
return packet;
}
private byte[] createEmptyRequest() throws Exception {
byte[] packet = new byte[PACKET_HEADER_LENGTH];
packet[1] = REQUEST;
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
return packet;
}
}

View File

@@ -0,0 +1,137 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.ImmediateExecutor;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.PacketWriter;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.Executor;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
public class SimplexOutgoingSessionTest extends BrambleTestCase {
private final Mockery context;
private final DatabaseComponent db;
private final Executor dbExecutor;
private final EventBus eventBus;
private final ContactId contactId;
private final MessageId messageId;
private final int maxLatency;
private final PacketWriter packetWriter;
public SimplexOutgoingSessionTest() {
context = new Mockery();
db = context.mock(DatabaseComponent.class);
dbExecutor = new ImmediateExecutor();
eventBus = context.mock(EventBus.class);
packetWriter = context.mock(PacketWriter.class);
contactId = new ContactId(234);
messageId = new MessageId(TestUtils.getRandomId());
maxLatency = Integer.MAX_VALUE;
}
@Test
public void testNothingToSend() throws Exception {
final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, maxLatency, packetWriter);
final Transaction noAckTxn = new Transaction(null, false);
final Transaction noMsgTxn = new Transaction(null, false);
context.checking(new Expectations() {{
// Add listener
oneOf(eventBus).addListener(session);
// No acks to send
oneOf(db).startTransaction(false);
will(returnValue(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null));
oneOf(db).commitTransaction(noAckTxn);
oneOf(db).endTransaction(noAckTxn);
// No messages to send
oneOf(db).startTransaction(false);
will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(null));
oneOf(db).commitTransaction(noMsgTxn);
oneOf(db).endTransaction(noMsgTxn);
// Flush the output stream
oneOf(packetWriter).flush();
// Remove listener
oneOf(eventBus).removeListener(session);
}});
session.run();
context.assertIsSatisfied();
}
@Test
public void testSomethingToSend() throws Exception {
final Ack ack = new Ack(Collections.singletonList(messageId));
final byte[] raw = new byte[1234];
final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, maxLatency, packetWriter);
final Transaction ackTxn = new Transaction(null, false);
final Transaction noAckTxn = new Transaction(null, false);
final Transaction msgTxn = new Transaction(null, false);
final Transaction noMsgTxn = new Transaction(null, false);
context.checking(new Expectations() {{
// Add listener
oneOf(eventBus).addListener(session);
// One ack to send
oneOf(db).startTransaction(false);
will(returnValue(ackTxn));
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(ack));
oneOf(db).commitTransaction(ackTxn);
oneOf(db).endTransaction(ackTxn);
oneOf(packetWriter).writeAck(ack);
// One message to send
oneOf(db).startTransaction(false);
will(returnValue(msgTxn));
oneOf(db).generateBatch(with(msgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(Arrays.asList(raw)));
oneOf(db).commitTransaction(msgTxn);
oneOf(db).endTransaction(msgTxn);
oneOf(packetWriter).writeMessage(raw);
// No more acks
oneOf(db).startTransaction(false);
will(returnValue(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null));
oneOf(db).commitTransaction(noAckTxn);
oneOf(db).endTransaction(noAckTxn);
// No more messages
oneOf(db).startTransaction(false);
will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(null));
oneOf(db).commitTransaction(noMsgTxn);
oneOf(db).endTransaction(noMsgTxn);
// Flush the output stream
oneOf(packetWriter).flush();
// Remove listener
oneOf(eventBus).removeListener(session);
}});
session.run();
context.assertIsSatisfied();
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.sync;
import org.briarproject.BriarTestCase;
import org.briarproject.TestUtils;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
@@ -39,7 +39,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SyncIntegrationTest extends BriarTestCase {
public class SyncIntegrationTest extends BrambleTestCase {
@Inject
GroupFactory groupFactory;

View File

@@ -1,6 +1,6 @@
package org.briarproject.bramble.sync;
import org.briarproject.TestSeedProviderModule;
import org.briarproject.bramble.TestSeedProviderModule;
import org.briarproject.bramble.crypto.CryptoModule;
import org.briarproject.bramble.transport.TransportModule;

View File

@@ -0,0 +1,90 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.Set;
import static org.briarproject.bramble.api.system.SeedProvider.SEED_BYTES;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class LinuxSeedProviderTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
}
@Test
public void testSeedAppearsSane() {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
Set<Bytes> seeds = new HashSet<Bytes>();
LinuxSeedProvider p = new LinuxSeedProvider();
for (int i = 0; i < 1000; i++) {
byte[] seed = p.getSeed();
assertEquals(SEED_BYTES, seed.length);
assertTrue(seeds.add(new Bytes(seed)));
}
}
@Test
public void testEntropyIsWrittenToPool() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the provider's entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
String path = urandom.getAbsolutePath();
LinuxSeedProvider p = new LinuxSeedProvider(path, "/dev/urandom");
p.getSeed();
// There should be 16 bytes from the clock, plus network interfaces
assertTrue(urandom.length() > 20);
}
@Test
public void testSeedIsReadFromPool() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate a seed
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
// Write the seed to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
FileOutputStream out = new FileOutputStream(urandom);
out.write(seed);
out.flush();
out.close();
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the provider reads the seed from the file
String path = urandom.getAbsolutePath();
LinuxSeedProvider p = new LinuxSeedProvider("/dev/urandom", path);
assertArrayEquals(seed, p.getSeed());
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
}
}

View File

@@ -0,0 +1,203 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.transport.StreamContext;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.DeterministicExecutor;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import static org.briarproject.bramble.TestUtils.getRandomBytes;
import static org.briarproject.bramble.TestUtils.getRandomId;
import static org.briarproject.bramble.TestUtils.getSecretKey;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertEquals;
public class KeyManagerImplTest extends BrambleTestCase {
private final Mockery context = new Mockery();
private final KeyManagerImpl keyManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final PluginConfig pluginConfig = context.mock(PluginConfig.class);
private final TransportKeyManagerFactory transportKeyManagerFactory =
context.mock(TransportKeyManagerFactory.class);
private final TransportKeyManager transportKeyManager =
context.mock(TransportKeyManager.class);
private final DeterministicExecutor executor = new DeterministicExecutor();
private final Transaction txn = new Transaction(null, false);
private final ContactId contactId = new ContactId(42);
private final ContactId inactiveContactId = new ContactId(43);
private final TransportId transportId = new TransportId("tId");
private final TransportId unknownTransportId = new TransportId("id");
private final StreamContext streamContext =
new StreamContext(contactId, transportId, getSecretKey(),
getSecretKey(), 1);
private final byte[] tag = getRandomBytes(TAG_LENGTH);
public KeyManagerImplTest() {
keyManager = new KeyManagerImpl(db, executor, pluginConfig,
transportKeyManagerFactory);
}
@Before
public void testStartService() throws Exception {
final Transaction txn = new Transaction(null, false);
AuthorId remoteAuthorId = new AuthorId(getRandomId());
Author remoteAuthor = new Author(remoteAuthorId, "author",
getRandomBytes(42));
AuthorId localAuthorId = new AuthorId(getRandomId());
final Collection<Contact> contacts = new ArrayList<Contact>();
contacts.add(new Contact(contactId, remoteAuthor, localAuthorId, true,
true));
contacts.add(new Contact(inactiveContactId, remoteAuthor, localAuthorId,
true, false));
final SimplexPluginFactory pluginFactory =
context.mock(SimplexPluginFactory.class);
final Collection<SimplexPluginFactory> factories = Collections
.singletonList(pluginFactory);
final int maxLatency = 1337;
context.checking(new Expectations() {{
oneOf(pluginConfig).getSimplexFactories();
will(returnValue(factories));
oneOf(pluginFactory).getId();
will(returnValue(transportId));
oneOf(pluginFactory).getMaxLatency();
will(returnValue(maxLatency));
oneOf(db).addTransport(txn, transportId, maxLatency);
oneOf(transportKeyManagerFactory)
.createTransportKeyManager(transportId, maxLatency);
will(returnValue(transportKeyManager));
oneOf(pluginConfig).getDuplexFactories();
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getContacts(txn);
will(returnValue(contacts));
oneOf(transportKeyManager).start(txn);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
keyManager.startService();
}
@Test
public void testAddContact() throws Exception {
final SecretKey secretKey = getSecretKey();
final long timestamp = 42L;
final boolean alice = true;
context.checking(new Expectations() {{
oneOf(transportKeyManager)
.addContact(txn, contactId, secretKey, timestamp, alice);
}});
keyManager.addContact(txn, contactId, secretKey, timestamp, alice);
context.assertIsSatisfied();
}
@Test
public void testGetStreamContextForInactiveContact() throws Exception {
assertEquals(null,
keyManager.getStreamContext(inactiveContactId, transportId));
}
@Test
public void testGetStreamContextForUnknownTransport() throws Exception {
assertEquals(null, keyManager
.getStreamContext(contactId, unknownTransportId));
}
@Test
public void testGetStreamContextForContact() throws Exception {
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(transportKeyManager).getStreamContext(txn, contactId);
will(returnValue(streamContext));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(streamContext,
keyManager.getStreamContext(contactId, transportId));
context.assertIsSatisfied();
}
@Test
public void testGetStreamContextForTagAndUnknownTransport()
throws Exception {
assertEquals(null,
keyManager.getStreamContext(unknownTransportId, tag));
}
@Test
public void testGetStreamContextForTag() throws Exception {
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(transportKeyManager).getStreamContext(txn, tag);
will(returnValue(streamContext));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
assertEquals(streamContext,
keyManager.getStreamContext(transportId, tag));
context.assertIsSatisfied();
}
@Test
public void testContactRemovedEvent() throws Exception {
ContactRemovedEvent event = new ContactRemovedEvent(contactId);
context.checking(new Expectations() {{
oneOf(transportKeyManager).removeContact(contactId);
}});
keyManager.eventOccurred(event);
executor.runUntilIdle();
assertEquals(null, keyManager.getStreamContext(contactId, transportId));
context.assertIsSatisfied();
}
@Test
public void testContactStatusChangedEvent() throws Exception {
ContactStatusChangedEvent event =
new ContactStatusChangedEvent(inactiveContactId, true);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(transportKeyManager).getStreamContext(txn, inactiveContactId);
will(returnValue(streamContext));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
keyManager.eventOccurred(event);
assertEquals(streamContext,
keyManager.getStreamContext(inactiveContactId, transportId));
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,108 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.transport.ReorderingWindow.Change;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class ReorderingWindowTest extends BrambleTestCase {
private static final int BITMAP_BYTES = REORDERING_WINDOW_SIZE / 8;
@Test
public void testBitmapConversion() {
for (int i = 0; i < 1000; i++) {
byte[] bitmap = TestUtils.getRandomBytes(BITMAP_BYTES);
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
assertArrayEquals(bitmap, window.getBitmap());
}
}
@Test
public void testWindowSlidesWhenFirstElementIsSeen() {
byte[] bitmap = new byte[BITMAP_BYTES];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
// Set the first element seen
Change change = window.setSeen(0L);
// The window should slide by one element
assertEquals(1L, window.getBase());
assertEquals(Collections.singletonList((long) REORDERING_WINDOW_SIZE),
change.getAdded());
assertEquals(Collections.singletonList(0L), change.getRemoved());
// All elements in the window should be unseen
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testWindowDoesNotSlideWhenElementBelowMidpointIsSeen() {
byte[] bitmap = new byte[BITMAP_BYTES];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
// Set an element below the midpoint seen
Change change = window.setSeen(1L);
// The window should not slide
assertEquals(0L, window.getBase());
assertEquals(Collections.emptyList(), change.getAdded());
assertEquals(Collections.singletonList(1L), change.getRemoved());
// The second element in the window should be seen
bitmap[0] = 0x40; // 0100 0000
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testWindowSlidesWhenElementAboveMidpointIsSeen() {
byte[] bitmap = new byte[BITMAP_BYTES];
ReorderingWindow window = new ReorderingWindow(0, bitmap);
long aboveMidpoint = REORDERING_WINDOW_SIZE / 2;
// Set an element above the midpoint seen
Change change = window.setSeen(aboveMidpoint);
// The window should slide by one element
assertEquals(1L, window.getBase());
assertEquals(Collections.singletonList((long) REORDERING_WINDOW_SIZE),
change.getAdded());
assertEquals(Arrays.asList(0L, aboveMidpoint), change.getRemoved());
// The highest element below the midpoint should be seen
bitmap[bitmap.length / 2 - 1] = (byte) 0x01; // 0000 0001
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testWindowSlidesUntilLowestElementIsUnseenWhenFirstElementIsSeen() {
byte[] bitmap = new byte[BITMAP_BYTES];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
window.setSeen(1L);
// Set the first element seen
Change change = window.setSeen(0L);
// The window should slide by two elements
assertEquals(2L, window.getBase());
assertEquals(Arrays.asList((long) REORDERING_WINDOW_SIZE,
(long) (REORDERING_WINDOW_SIZE + 1)), change.getAdded());
assertEquals(Collections.singletonList(0L), change.getRemoved());
// All elements in the window should be unseen
assertArrayEquals(bitmap, window.getBitmap());
}
@Test
public void testWindowSlidesUntilLowestElementIsUnseenWhenElementAboveMidpointIsSeen() {
byte[] bitmap = new byte[BITMAP_BYTES];
ReorderingWindow window = new ReorderingWindow(0L, bitmap);
window.setSeen(1L);
long aboveMidpoint = REORDERING_WINDOW_SIZE / 2;
// Set an element above the midpoint seen
Change change = window.setSeen(aboveMidpoint);
// The window should slide by two elements
assertEquals(2L, window.getBase());
assertEquals(Arrays.asList((long) REORDERING_WINDOW_SIZE,
(long) (REORDERING_WINDOW_SIZE + 1)), change.getAdded());
assertEquals(Arrays.asList(0L, aboveMidpoint), change.getRemoved());
// The second-highest element below the midpoint should be seen
bitmap[bitmap.length / 2 - 1] = (byte) 0x02; // 0000 0010
assertArrayEquals(bitmap, window.getBitmap());
}
}

View File

@@ -0,0 +1,108 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.junit.Assert.assertEquals;
public class StreamReaderImplTest extends BrambleTestCase {
@Test
public void testEmptyFramesAreSkipped() throws Exception {
Mockery context = new Mockery();
final StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(2)); // Non-empty frame with two payload bytes
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
StreamReaderImpl r = new StreamReaderImpl(decrypter);
assertEquals(0, r.read()); // Skip the first empty frame, read a byte
assertEquals(0, r.read()); // Read another byte
assertEquals(-1, r.read()); // Skip the second empty frame, reach EOF
assertEquals(-1, r.read()); // Still at EOF
context.assertIsSatisfied();
r.close();
}
@Test
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
Mockery context = new Mockery();
final StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(2)); // Non-empty frame with two payload bytes
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(0)); // Empty frame
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
StreamReaderImpl r = new StreamReaderImpl(decrypter);
byte[] buf = new byte[MAX_PAYLOAD_LENGTH];
// Skip the first empty frame, read the two payload bytes
assertEquals(2, r.read(buf));
// Skip the second empty frame, reach EOF
assertEquals(-1, r.read(buf));
// Still at EOF
assertEquals(-1, r.read(buf));
context.assertIsSatisfied();
r.close();
}
@Test
public void testMultipleReadsPerFrame() throws Exception {
Mockery context = new Mockery();
final StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
StreamReaderImpl r = new StreamReaderImpl(decrypter);
byte[] buf = new byte[MAX_PAYLOAD_LENGTH / 2];
// Read the first half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf));
// Read the second half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf));
// Reach EOF
assertEquals(-1, r.read(buf, 0, buf.length));
context.assertIsSatisfied();
r.close();
}
@Test
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
Mockery context = new Mockery();
final StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(MAX_PAYLOAD_LENGTH)); // Nice long frame
oneOf(decrypter).readFrame(with(any(byte[].class)));
will(returnValue(-1)); // No more frames
}});
StreamReaderImpl r = new StreamReaderImpl(decrypter);
byte[] buf = new byte[MAX_PAYLOAD_LENGTH];
// Read the first half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf, MAX_PAYLOAD_LENGTH / 2,
MAX_PAYLOAD_LENGTH / 2));
// Read the second half of the payload
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf, 123,
MAX_PAYLOAD_LENGTH / 2));
// Reach EOF
assertEquals(-1, r.read(buf, 0, buf.length));
context.assertIsSatisfied();
r.close();
}
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class StreamReaderWriterIntegrationTest extends BrambleTestCase {
@Test
public void testWriteAndRead() throws Exception {
// Generate a random tag
byte[] tag = TestUtils.getRandomBytes(TAG_LENGTH);
// Generate two frames with random payloads
byte[] payload1 = TestUtils.getRandomBytes(123);
byte[] payload2 = TestUtils.getRandomBytes(321);
// Write the tag and the frames
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypter encrypter = new TestStreamEncrypter(out, tag);
OutputStream streamWriter = new StreamWriterImpl(encrypter);
streamWriter.write(payload1);
streamWriter.flush();
streamWriter.write(payload2);
streamWriter.flush();
byte[] output = out.toByteArray();
assertEquals(TAG_LENGTH + STREAM_HEADER_LENGTH
+ FRAME_HEADER_LENGTH + payload1.length + MAC_LENGTH
+ FRAME_HEADER_LENGTH + payload2.length + MAC_LENGTH,
output.length);
// Read the tag back
ByteArrayInputStream in = new ByteArrayInputStream(output);
byte[] recoveredTag = new byte[tag.length];
read(in, recoveredTag);
assertArrayEquals(tag, recoveredTag);
// Read the frames back
StreamDecrypter decrypter = new TestStreamDecrypter(in);
InputStream streamReader = new StreamReaderImpl(decrypter);
byte[] recoveredPayload1 = new byte[payload1.length];
read(streamReader, recoveredPayload1);
assertArrayEquals(payload1, recoveredPayload1);
byte[] recoveredPayload2 = new byte[payload2.length];
read(streamReader, recoveredPayload2);
assertArrayEquals(payload2, recoveredPayload2);
streamWriter.close();
streamReader.close();
}
private void read(InputStream in, byte[] dest) throws IOException {
int offset = 0;
while (offset < dest.length) {
int read = in.read(dest, offset, dest.length - offset);
if (read == -1) break;
offset += read;
}
}
}

View File

@@ -0,0 +1,162 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.junit.Assert.assertEquals;
public class StreamWriterImplTest extends BrambleTestCase {
@Test
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
Mockery context = new Mockery();
final StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
context.checking(new Expectations() {{
// Write an empty final frame
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(0),
with(0), with(true));
// Flush the stream
oneOf(encrypter).flush();
}});
StreamWriterImpl w = new StreamWriterImpl(encrypter);
w.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithoutBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
final StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
// Write a non-final frame with an empty payload
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(0),
with(0), with(false));
// Flush the stream
oneOf(encrypter).flush();
}});
w.flush();
context.assertIsSatisfied();
// Clean up
context.checking(new Expectations() {{
// Closing the writer writes a final frame and flushes again
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(0),
with(0), with(true));
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
final StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
// Write a non-final frame with one payload byte
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(1),
with(0), with(false));
// Flush the stream
oneOf(encrypter).flush();
}});
w.write(0);
w.flush();
context.assertIsSatisfied();
// Clean up
context.checking(new Expectations() {{
// Closing the writer writes a final frame and flushes again
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(0),
with(0), with(true));
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testSingleByteWritesWriteFullFrame() throws Exception {
Mockery context = new Mockery();
final StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
// Write a full non-final frame
oneOf(encrypter).writeFrame(with(any(byte[].class)),
with(MAX_PAYLOAD_LENGTH), with(0), with(false));
}});
for (int i = 0; i < MAX_PAYLOAD_LENGTH; i++) w.write(0);
context.assertIsSatisfied();
// Clean up
context.checking(new Expectations() {{
// Closing the writer writes a final frame and flushes again
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(0),
with(0), with(true));
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testMultiByteWritesWriteFullFrames() throws Exception {
Mockery context = new Mockery();
final StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
// Write two full non-final frames
exactly(2).of(encrypter).writeFrame(with(any(byte[].class)),
with(MAX_PAYLOAD_LENGTH), with(0), 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];
w.write(b);
w.write(b);
w.write(b);
w.write(b);
context.assertIsSatisfied();
// Clean up
context.checking(new Expectations() {{
// Closing the writer writes a final frame and flushes again
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(0),
with(0), with(true));
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
Mockery context = new Mockery();
final StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
// Write two full non-final frames
exactly(2).of(encrypter).writeFrame(with(any(byte[].class)),
with(MAX_PAYLOAD_LENGTH), with(0), with(false));
// Write a final frame with a one-byte payload
oneOf(encrypter).writeFrame(with(any(byte[].class)), with(1),
with(0), with(true));
// Flush the stream
oneOf(encrypter).flush();
}});
// Write two full payloads using one large multi-byte write
byte[] b = new byte[MAX_PAYLOAD_LENGTH * 2 + 1];
w.write(b);
// There should be one byte left in the buffer
w.close();
context.assertIsSatisfied();
}
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.ByteUtils;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
@NotNullByDefault
class TestStreamDecrypter implements StreamDecrypter {
private final InputStream in;
private final byte[] frame;
private boolean readStreamHeader = true, finalFrame = false;
TestStreamDecrypter(InputStream in) {
this.in = in;
frame = new byte[MAX_FRAME_LENGTH];
}
@Override
public int readFrame(byte[] payload) throws IOException {
if (finalFrame) return -1;
if (readStreamHeader) readStreamHeader();
int offset = 0;
while (offset < FRAME_HEADER_LENGTH) {
int read = in.read(frame, offset, FRAME_HEADER_LENGTH - offset);
if (read == -1) throw new EOFException();
offset += read;
}
finalFrame = (frame[0] & 0x80) == 0x80;
int payloadLength = ByteUtils.readUint16(frame, 0) & 0x7FFF;
int paddingLength = ByteUtils.readUint16(frame, INT_16_BYTES);
int frameLength = FRAME_HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
while (offset < frameLength) {
int read = in.read(frame, offset, frameLength - offset);
if (read == -1) throw new EOFException();
offset += read;
}
System.arraycopy(frame, FRAME_HEADER_LENGTH, payload, 0, payloadLength);
return payloadLength;
}
private void readStreamHeader() throws IOException {
byte[] streamHeader = new byte[STREAM_HEADER_LENGTH];
int offset = 0;
while (offset < STREAM_HEADER_LENGTH) {
int read = in.read(streamHeader, offset,
STREAM_HEADER_LENGTH - offset);
if (read == -1) throw new EOFException();
offset += read;
}
readStreamHeader = false;
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.ByteUtils;
import java.io.IOException;
import java.io.OutputStream;
import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
@NotNullByDefault
class TestStreamEncrypter implements StreamEncrypter {
private final OutputStream out;
private final byte[] tag;
private boolean writeTagAndHeader = true;
TestStreamEncrypter(OutputStream out, byte[] tag) {
this.out = out;
this.tag = tag;
}
@Override
public void writeFrame(byte[] payload, int payloadLength,
int paddingLength, boolean finalFrame) throws IOException {
if (writeTagAndHeader) writeTagAndHeader();
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
ByteUtils.writeUint16(payloadLength, frameHeader, 0);
ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES);
if (finalFrame) frameHeader[0] |= 0x80;
out.write(frameHeader);
out.write(payload, 0, payloadLength);
out.write(new byte[paddingLength]);
out.write(new byte[MAC_LENGTH]);
}
@Override
public void flush() throws IOException {
if (writeTagAndHeader) writeTagAndHeader();
out.flush();
}
private void writeTagAndHeader() throws IOException {
out.write(tag);
out.write(new byte[STREAM_HEADER_LENGTH]);
writeTagAndHeader = false;
}
}

View File

@@ -0,0 +1,513 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.RunAction;
import org.briarproject.bramble.BrambleTestCase;
import org.briarproject.bramble.TestUtils;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeys;
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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TransportKeyManagerImplTest extends BrambleTestCase {
private final TransportId transportId = new TransportId("id");
private final long maxLatency = 30 * 1000; // 30 seconds
private final long rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
private final ContactId contactId = new ContactId(123);
private final ContactId contactId1 = new ContactId(234);
private final SecretKey tagKey = TestUtils.getSecretKey();
private final SecretKey headerKey = TestUtils.getSecretKey();
private final SecretKey masterKey = TestUtils.getSecretKey();
private final Random random = new Random();
@Test
public void testKeysAreRotatedAtStartup() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final Map<ContactId, TransportKeys> loaded =
new LinkedHashMap<ContactId, TransportKeys>();
final TransportKeys shouldRotate = createTransportKeys(900, 0);
final TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
loaded.put(contactId, shouldRotate);
loaded.put(contactId1, shouldNotRotate);
final TransportKeys rotated = createTransportKeys(1000, 0);
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
// Get the current time (1 ms after start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000 + 1));
// Load the transport keys
oneOf(db).getTransportKeys(txn, transportId);
will(returnValue(loaded));
// Rotate the transport keys
oneOf(crypto).rotateTransportKeys(shouldRotate, 1000);
will(returnValue(rotated));
oneOf(crypto).rotateTransportKeys(shouldNotRotate, 1000);
will(returnValue(shouldNotRotate));
// Encode the tags (3 sets per contact)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(6).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Save the keys that were rotated
oneOf(db).updateTransportKeys(txn,
Collections.singletonMap(contactId, rotated));
// Schedule key rotation at the start of the next rotation period
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(rotationPeriodLength - 1), with(MILLISECONDS));
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
transportKeyManager.start(txn);
context.assertIsSatisfied();
}
@Test
public void testKeysAreRotatedWhenAddingContact() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final boolean alice = true;
final TransportKeys transportKeys = createTransportKeys(999, 0);
final TransportKeys rotated = createTransportKeys(1000, 0);
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 999,
alice);
will(returnValue(transportKeys));
// Get the current time (1 ms after start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000 + 1));
// Rotate the transport keys
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(rotated));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, rotated);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
// The timestamp is 1 ms before the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000 - 1;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
context.assertIsSatisfied();
}
@Test
public void testOutgoingStreamContextIsNullIfContactIsNotFound()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final Transaction txn = new Transaction(null, false);
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
assertNull(transportKeyManager.getStreamContext(txn, contactId));
context.assertIsSatisfied();
}
@Test
public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final boolean alice = true;
// The stream counter has been exhausted
final TransportKeys transportKeys = createTransportKeys(1000,
MAX_32_BIT_UNSIGNED + 1);
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
assertNull(transportKeyManager.getStreamContext(txn, contactId));
context.assertIsSatisfied();
}
@Test
public void testOutgoingStreamCounterIsIncremented() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final boolean alice = true;
// The stream counter can be used one more time before being exhausted
final TransportKeys transportKeys = createTransportKeys(1000,
MAX_32_BIT_UNSIGNED);
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
// Increment the stream counter
oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
// The first request should return a stream context
StreamContext ctx = transportKeyManager.getStreamContext(txn,
contactId);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertEquals(tagKey, ctx.getTagKey());
assertEquals(headerKey, ctx.getHeaderKey());
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
// The second request should return null, the counter is exhausted
assertNull(transportKeyManager.getStreamContext(txn, contactId));
context.assertIsSatisfied();
}
@Test
public void testIncomingStreamContextIsNullIfTagIsNotFound()
throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final boolean alice = true;
final TransportKeys transportKeys = createTransportKeys(1000, 0);
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
assertNull(transportKeyManager.getStreamContext(txn,
new byte[TAG_LENGTH]));
context.assertIsSatisfied();
}
@Test
public void testTagIsNotRecognisedTwice() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final boolean alice = true;
final TransportKeys transportKeys = createTransportKeys(1000, 0);
// Keep a copy of the tags
final List<byte[]> tags = new ArrayList<byte[]>();
final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
alice);
will(returnValue(transportKeys));
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction(tags));
}
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
// Encode a new tag after sliding the window
oneOf(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with((long) REORDERING_WINDOW_SIZE));
will(new EncodeTagAction(tags));
// Save the reordering window (previous rotation period, base 1)
oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
1, new byte[REORDERING_WINDOW_SIZE / 8]);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
// The timestamp is at the start of rotation period 1000
long timestamp = rotationPeriodLength * 1000;
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
alice);
// Use the first tag (previous rotation period, stream number 0)
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
byte[] tag = tags.get(0);
// The first request should return a stream context
StreamContext ctx = transportKeyManager.getStreamContext(txn, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertEquals(tagKey, ctx.getTagKey());
assertEquals(headerKey, ctx.getHeaderKey());
assertEquals(0L, ctx.getStreamNumber());
// Another tag should have been encoded
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
// The second request should return null, the tag has already been used
assertNull(transportKeyManager.getStreamContext(txn, tag));
context.assertIsSatisfied();
}
@Test
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
Mockery context = new Mockery();
final DatabaseComponent db = context.mock(DatabaseComponent.class);
final CryptoComponent crypto = context.mock(CryptoComponent.class);
final Executor dbExecutor = context.mock(Executor.class);
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final Clock clock = context.mock(Clock.class);
final TransportKeys transportKeys = createTransportKeys(1000, 0);
final Map<ContactId, TransportKeys> loaded =
Collections.singletonMap(contactId, transportKeys);
final TransportKeys rotated = createTransportKeys(1001, 0);
final Transaction txn = new Transaction(null, false);
final Transaction txn1 = new Transaction(null, false);
context.checking(new Expectations() {{
// Get the current time (the start of rotation period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1000));
// Load the transport keys
oneOf(db).getTransportKeys(txn, transportId);
will(returnValue(loaded));
// Rotate the transport keys (the keys are unaffected)
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Schedule key rotation at the start of the next rotation period
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(rotationPeriodLength), with(MILLISECONDS));
will(new RunAction());
oneOf(dbExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
// Start a transaction for key rotation
oneOf(db).startTransaction(false);
will(returnValue(txn1));
// Get the current time (the start of rotation period 1001)
oneOf(clock).currentTimeMillis();
will(returnValue(rotationPeriodLength * 1001));
// Rotate the transport keys
oneOf(crypto).rotateTransportKeys(with(any(TransportKeys.class)),
with(1001L));
will(returnValue(rotated));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(i));
will(new EncodeTagAction());
}
// Save the keys that were rotated
oneOf(db).updateTransportKeys(txn1,
Collections.singletonMap(contactId, rotated));
// Schedule key rotation at the start of the next rotation period
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(rotationPeriodLength), with(MILLISECONDS));
// Commit the key rotation transaction
oneOf(db).commitTransaction(txn1);
oneOf(db).endTransaction(txn1);
}});
TransportKeyManager
transportKeyManager = new TransportKeyManagerImpl(db,
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
transportKeyManager.start(txn);
context.assertIsSatisfied();
}
private TransportKeys createTransportKeys(long rotationPeriod,
long streamCounter) {
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
rotationPeriod - 1);
IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
rotationPeriod);
IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
rotationPeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
rotationPeriod, streamCounter);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
}
private class EncodeTagAction implements Action {
private final Collection<byte[]> tags;
private EncodeTagAction() {
tags = null;
}
private EncodeTagAction(Collection<byte[]> tags) {
this.tags = tags;
}
@Override
public Object invoke(Invocation invocation) throws Throwable {
byte[] tag = (byte[]) invocation.getParameter(0);
random.nextBytes(tag);
if (tags != null) tags.add(tag);
return null;
}
@Override
public void describeTo(Description description) {
description.appendText("encodes a tag");
}
}
}

View File

@@ -0,0 +1,210 @@
package org.briarproject.bramble.util;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class ByteUtilsTest extends BrambleTestCase {
@Test
public void testReadUint16() {
byte[] b = StringUtils.fromHexString("00000000");
assertEquals(0, ByteUtils.readUint16(b, 1));
b = StringUtils.fromHexString("00000100");
assertEquals(1, ByteUtils.readUint16(b, 1));
b = StringUtils.fromHexString("007FFF00");
assertEquals(Short.MAX_VALUE, ByteUtils.readUint16(b, 1));
b = StringUtils.fromHexString("00FFFF00");
assertEquals(65535, ByteUtils.readUint16(b, 1));
}
@Test(expected = IllegalArgumentException.class)
public void testReadUint16ValidatesArguments1() {
ByteUtils.readUint16(new byte[1], 0);
}
@Test(expected = IllegalArgumentException.class)
public void testReadUint16ValidatesArguments2() {
ByteUtils.readUint16(new byte[2], 1);
}
@Test
public void testReadUint32() {
byte[] b = StringUtils.fromHexString("000000000000");
assertEquals(0, ByteUtils.readUint32(b, 1));
b = StringUtils.fromHexString("000000000100");
assertEquals(1, ByteUtils.readUint32(b, 1));
b = StringUtils.fromHexString("007FFFFFFF00");
assertEquals(Integer.MAX_VALUE, ByteUtils.readUint32(b, 1));
b = StringUtils.fromHexString("00FFFFFFFF00");
assertEquals(4294967295L, ByteUtils.readUint32(b, 1));
}
@Test(expected = IllegalArgumentException.class)
public void testReadUint32ValidatesArguments1() {
ByteUtils.readUint32(new byte[3], 0);
}
@Test(expected = IllegalArgumentException.class)
public void testReadUint32ValidatesArguments2() {
ByteUtils.readUint32(new byte[4], 1);
}
@Test
public void testReadUint64() {
byte[] b = StringUtils.fromHexString("00000000000000000000");
assertEquals(0L, ByteUtils.readUint64(b, 1));
b = StringUtils.fromHexString("00000000000000000100");
assertEquals(1L, ByteUtils.readUint64(b, 1));
b = StringUtils.fromHexString("007FFFFFFFFFFFFFFF00");
assertEquals(Long.MAX_VALUE, ByteUtils.readUint64(b, 1));
b = StringUtils.fromHexString("00800000000000000000");
assertEquals(Long.MIN_VALUE, ByteUtils.readUint64(b, 1));
b = StringUtils.fromHexString("00FFFFFFFFFFFFFFFF00");
assertEquals(-1L, ByteUtils.readUint64(b, 1));
}
@Test(expected = IllegalArgumentException.class)
public void testReadUint64ValidatesArguments1() {
ByteUtils.readUint64(new byte[7], 0);
}
@Test(expected = IllegalArgumentException.class)
public void testReadUint64ValidatesArguments2() {
ByteUtils.readUint64(new byte[8], 1);
}
@Test
public void testWriteUint16() {
byte[] b = new byte[4];
ByteUtils.writeUint16(0, b, 1);
assertEquals("00000000", StringUtils.toHexString(b));
ByteUtils.writeUint16(1, b, 1);
assertEquals("00000100", StringUtils.toHexString(b));
ByteUtils.writeUint16(Short.MAX_VALUE, b, 1);
assertEquals("007FFF00", StringUtils.toHexString(b));
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
assertEquals("00FFFF00", StringUtils.toHexString(b));
}
@Test
public void testWriteUint16ValidatesArguments() {
try {
ByteUtils.writeUint16(0, new byte[1], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint16(0, new byte[2], 1);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint16(-1, new byte[2], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
}
@Test
public void testWriteUint32() {
byte[] b = new byte[6];
ByteUtils.writeUint32(0, b, 1);
assertEquals("000000000000", StringUtils.toHexString(b));
ByteUtils.writeUint32(1, b, 1);
assertEquals("000000000100", StringUtils.toHexString(b));
ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1);
assertEquals("007FFFFFFF00", StringUtils.toHexString(b));
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
assertEquals("00FFFFFFFF00", StringUtils.toHexString(b));
}
@Test
public void testWriteUint32ValidatesArguments() {
try {
ByteUtils.writeUint32(0, new byte[3], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint32(0, new byte[4], 1);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint32(-1, new byte[4], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
}
@Test
public void testWriteUint64() {
byte[] b = new byte[10];
ByteUtils.writeUint64(0, b, 1);
assertEquals("00000000000000000000", StringUtils.toHexString(b));
ByteUtils.writeUint64(1, b, 1);
assertEquals("00000000000000000100", StringUtils.toHexString(b));
ByteUtils.writeUint64(Long.MAX_VALUE, b, 1);
assertEquals("007FFFFFFFFFFFFFFF00", StringUtils.toHexString(b));
}
@Test
public void testWriteUint64ValidatesArguments() {
try {
ByteUtils.writeUint64(0, new byte[7], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint64(0, new byte[8], 1);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
try {
ByteUtils.writeUint64(-1, new byte[8], 0);
fail();
} catch (IllegalArgumentException expected) {
// Expected
}
}
@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,227 @@
package org.briarproject.bramble.util;
import org.briarproject.bramble.BrambleTestCase;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
public class StringUtilsTest extends BrambleTestCase {
@Test
public void testToHexString() {
byte[] b = new byte[] {
0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF
};
String expected = "000102037F800A0B0C0D0EFF";
assertEquals(expected, StringUtils.toHexString(b));
}
@Test
public void testToHexStringEmptyInput() {
assertEquals("", StringUtils.toHexString(new byte[0]));
}
@Test(expected = IllegalArgumentException.class)
public void testFromHexStringRejectsInvalidLength() {
StringUtils.fromHexString("12345");
}
@Test(expected = IllegalArgumentException.class)
public void testFromHexStringRejectsInvalidCharacter() {
StringUtils.fromHexString("ABCDEFGH");
}
@Test
public void testFromHexStringUppercase() {
String s = "000102037F800A0B0C0D0EFF";
byte[] expected = new byte[] {
0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF
};
assertArrayEquals(expected, StringUtils.fromHexString(s));
}
@Test
public void testFromHexStringLowercase() {
String s = "000102037f800a0b0c0d0eff";
byte[] expected = new byte[] {
0x00, 0x01, 0x02, 0x03, 0x7F, (byte) 0x80,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, (byte) 0xFF
};
assertArrayEquals(expected, StringUtils.fromHexString(s));
}
@Test
public void testFromHexStringEmptyInput() {
assertArrayEquals(new byte[0], StringUtils.fromHexString(""));
}
@Test
public void testToUtf8EncodesNullCharacterAsStandardUtf8() {
// The Unicode null character should be encoded as a single null byte,
// not as two bytes as in CESU-8 and modified UTF-8
String s = "\u0000";
assertArrayEquals(new byte[1], StringUtils.toUtf8(s));
}
@Test
public void testToUtf8EncodesSupplementaryCharactersAsStandardUtf8() {
// A supplementary character should be encoded as four bytes, not as a
// surrogate pair as in CESU-8 and modified UTF-8
String s = "\u0045\u0205\uD801\uDC00";
byte[] expected = new byte[] {
0x45, // U+0045
(byte) 0xC8, (byte) 0x85, // U+0205
(byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80 // U+10400
};
assertArrayEquals(expected, StringUtils.toUtf8(s));
}
@Test
public void testToUtf8EmptyInput() {
assertArrayEquals(new byte[0], StringUtils.toUtf8(""));
}
@Test
public void testFromUtf8AcceptsNullCharacterUsingStandardUtf8() {
// The UTF-8 encoding of the null character is valid
assertEquals("\u0000", StringUtils.fromUtf8(new byte[1]));
}
@Test
public void testFromUtf8RemovesNullCharacterUsingModifiedUtf8() {
// The modified UTF-8 encoding of the null character is not valid
byte[] b = new byte[] {
(byte) 0xC0, (byte) 0x80, // Null character as modified UTF-8
(byte) 0xC8, (byte) 0x85 // U+0205
};
// Conversion should ignore the invalid character and return the rest
String expected = "\u0205";
assertEquals(expected, StringUtils.fromUtf8(b));
}
@Test
public void testFromUtf8AcceptsSupplementaryCharacterUsingStandardUtf8() {
// The UTF-8 encoding of a supplementary character is valid and should
// be converted to a surrogate pair
byte[] b = new byte[] {
(byte) 0xF0, (byte) 0x90, (byte) 0x90, (byte) 0x80, // U+10400
(byte) 0xC8, (byte) 0x85 // U+0205
};
String expected = "\uD801\uDC00\u0205"; // Surrogate pair
assertEquals(expected, StringUtils.fromUtf8(b));
}
@Test
public void testFromUtf8RemovesSupplementaryCharacterUsingModifiedUtf8() {
// The CESU-8 or modified UTF-8 encoding of a supplementary character
// is not valid
byte[] b = new byte[] {
(byte) 0xED, (byte) 0xA0, (byte) 0x81, // U+10400 as CSEU-8
(byte) 0xED, (byte) 0xB0, (byte) 0x80,
(byte) 0xC8, (byte) 0x85 // U+0205
};
// Conversion should ignore the invalid character and return the rest
String expected = "\u0205";
assertEquals(expected, StringUtils.fromUtf8(b));
}
@Test
public void testFromUtf8EmptyInput() {
assertEquals("", StringUtils.fromUtf8(new byte[0]));
}
@Test
public void testTruncateUtf8ReturnsArgumentIfNotTruncated() {
String s = "Hello";
assertSame(s, StringUtils.truncateUtf8(s, 5));
}
@Test
public void testTruncateUtf8ChecksUtf8LengthNotStringLength() {
String s = "H\u0205llo";
assertEquals(5, s.length());
assertEquals(6, StringUtils.toUtf8(s).length);
String expected = "H\u0205ll"; // Sixth byte removed
assertEquals(expected, StringUtils.truncateUtf8(s, 5));
}
@Test
public void testTruncateUtf8RemovesTruncatedCharacter() {
String s = "\u0205\u0205"; // String requires four bytes
String expected = "\u0205"; // Partial character removed
String truncated = StringUtils.truncateUtf8(s, 3);
assertEquals(expected, truncated);
// Converting the truncated string should not exceed the max length
assertEquals(2, StringUtils.toUtf8(truncated).length);
}
@Test
public void testTruncateUtf8RemovesTruncatedSurrogatePair() {
String s = "\u0205\uD801\uDC00"; // String requires six bytes
String expected = "\u0205"; // Partial character removed
String truncated = StringUtils.truncateUtf8(s, 3);
assertEquals(expected, truncated);
// Converting the truncated string should not exceed the max length
assertEquals(2, StringUtils.toUtf8(truncated).length);
}
@Test
public void testTruncateUtf8EmptyInput() {
assertEquals("", StringUtils.truncateUtf8("", 123));
}
@Test(expected = IllegalArgumentException.class)
public void testMacToBytesRejectsShortMac() {
StringUtils.macToBytes("00:00:00:00:00");
}
@Test(expected = IllegalArgumentException.class)
public void testMacToBytesRejectsLongMac() {
StringUtils.macToBytes("00:00:00:00:00:00:00");
}
@Test(expected = IllegalArgumentException.class)
public void testMacToBytesRejectsInvalidCharacter() {
StringUtils.macToBytes("00:00:00:00:00:0g");
}
@Test(expected = IllegalArgumentException.class)
public void testMacToBytesRejectsInvalidFormat() {
StringUtils.macToBytes("0:000:00:00:00:00");
}
@Test
public void testMacToBytesUpperCase() {
byte[] expected = new byte[] {0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F};
String mac = "0A:1B:2C:3D:4E:5F";
assertArrayEquals(expected, StringUtils.macToBytes(mac));
}
@Test
public void testMacToBytesLowerCase() {
byte[] expected = new byte[] {0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F};
String mac = "0a:1b:2c:3d:4e:5f";
assertArrayEquals(expected, StringUtils.macToBytes(mac));
}
@Test(expected = IllegalArgumentException.class)
public void testMacToStringRejectsShortMac() {
StringUtils.macToString(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testMacToStringRejectsLongMac() {
StringUtils.macToString(new byte[7]);
}
@Test
public void testMacToString() {
byte[] mac = new byte[] {0x0a, 0x1b, 0x2c, 0x3d, 0x4e, 0x5f};
String expected = "0A:1B:2C:3D:4E:5F";
assertEquals(expected, StringUtils.macToString(mac));
}
}