Store each connection window slot as a database row.

This is less memory-efficient but necessary for the coming forward
secrecy changes.
This commit is contained in:
akwizgran
2011-11-15 13:08:20 +00:00
parent cf49a28c95
commit df054b1743
19 changed files with 152 additions and 140 deletions

View File

@@ -48,4 +48,7 @@ public interface ProtocolConstants {
/** The length of a message's random salt in bytes. */ /** The length of a message's random salt in bytes. */
static final int SALT_LENGTH = 8; static final int SALT_LENGTH = 8;
/** The size of the connection reordering window. */
static final int CONNECTION_WINDOW_SIZE = 32;
} }

View File

@@ -4,13 +4,9 @@ import java.util.Collection;
public interface ConnectionWindow { public interface ConnectionWindow {
long getCentre();
int getBitmap();
boolean isSeen(long connection); boolean isSeen(long connection);
void setSeen(long connection); void setSeen(long connection);
Collection<Long> getUnseenConnectionNumbers(); Collection<Long> getUnseen();
} }

View File

@@ -1,6 +1,10 @@
package net.sf.briar.api.transport; package net.sf.briar.api.transport;
import java.util.Collection;
public interface ConnectionWindowFactory { public interface ConnectionWindowFactory {
ConnectionWindow createConnectionWindow(long centre, int bitmap); ConnectionWindow createConnectionWindow();
ConnectionWindow createConnectionWindow(Collection<Long> unseen);
} }

View File

@@ -5,7 +5,7 @@ import net.sf.briar.api.protocol.TransportIndex;
public interface StreamConnectionFactory { public interface StreamConnectionFactory {
void createIncomingConnection(TransportIndex i, ContactId c, void createIncomingConnection(TransportIndex i, ContactId c,
StreamTransportConnection s, byte[] encryptedIv); StreamTransportConnection s, byte[] encryptedIv);
void createOutgoingConnection(TransportIndex i, ContactId c, void createOutgoingConnection(TransportIndex i, ContactId c,

View File

@@ -18,6 +18,5 @@ public class CryptoModule extends AbstractModule {
// FIXME: Use a real key // FIXME: Use a real key
bind(SecretKey.class).annotatedWith(SecretStorageKey.class).toInstance( bind(SecretKey.class).annotatedWith(SecretStorageKey.class).toInstance(
new SecretKeySpec(new byte[32], "AES")); new SecretKeySpec(new byte[32], "AES"));
} }
} }

View File

@@ -28,7 +28,7 @@ import net.sf.briar.api.transport.ConnectionWindow;
* obtained by calling startTransaction(). Every transaction must be * obtained by calling startTransaction(). Every transaction must be
* terminated by calling either abortTransaction() or commitTransaction(), * terminated by calling either abortTransaction() or commitTransaction(),
* even if an exception is thrown. * even if an exception is thrown.
* * <p>
* Locking is provided by the DatabaseComponent implementation. To prevent * Locking is provided by the DatabaseComponent implementation. To prevent
* deadlock, locks must be acquired in the following order: * deadlock, locks must be acquired in the following order:
* <ul> * <ul>

View File

@@ -1597,7 +1597,7 @@ DatabaseCleaner.Callback {
} }
/** /**
* Removes the given message (and all associated state) from the database. * Removes the given message (and all associated state) from the database.
* <p> * <p>
* Locking: contact read, message write, messageStatus write. * Locking: contact read, message write, messageStatus write.
*/ */

View File

@@ -214,14 +214,21 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
private static final String CREATE_CONNECTIONS =
"CREATE TABLE connections"
+ " (contactId INT NOT NULL,"
+ " index INT NOT NULL,"
+ " outgoing BIGINT NOT NULL,"
+ " PRIMARY KEY (contactId, index),"
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_CONNECTION_WINDOWS = private static final String CREATE_CONNECTION_WINDOWS =
"CREATE TABLE connectionWindows" "CREATE TABLE connectionWindows"
+ " (contactId INT NOT NULL," + " (contactId INT NOT NULL,"
+ " index INT NOT NULL," + " index INT NOT NULL,"
+ " centre BIGINT NOT NULL," + " unseen BIGINT NOT NULL,"
+ " bitmap INT NOT NULL," + " PRIMARY KEY (contactId, index, unseen),"
+ " outgoing BIGINT NOT NULL,"
+ " PRIMARY KEY (contactId, index),"
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)" + " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
@@ -342,6 +349,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_PROPS)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_PROPS));
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS)); s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS));
s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_PROPS)); s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORT_PROPS));
s.executeUpdate(insertTypeNames(CREATE_CONNECTIONS));
s.executeUpdate(insertTypeNames(CREATE_CONNECTION_WINDOWS)); s.executeUpdate(insertTypeNames(CREATE_CONNECTION_WINDOWS));
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_TIMESTAMPS)); s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_TIMESTAMPS));
s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_TIMESTAMPS)); s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_TIMESTAMPS));
@@ -895,19 +903,19 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT outgoing FROM connectionWindows" String sql = "SELECT outgoing FROM connections"
+ " WHERE contactId = ? AND index = ?"; + " WHERE contactId = ? AND index = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, i.getInt()); ps.setInt(2, i.getInt());
rs = ps.executeQuery(); rs = ps.executeQuery();
if(rs.next()) { if(rs.next()) {
// A connection window row exists - update it // A connection row exists - update it
long outgoing = rs.getLong(1); long outgoing = rs.getLong(1);
if(rs.next()) throw new DbStateException(); if(rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
sql = "UPDATE connectionWindows SET outgoing = ?" sql = "UPDATE connections SET outgoing = ?"
+ " WHERE contactId = ? AND index = ?"; + " WHERE contactId = ? AND index = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setLong(1, outgoing + 1); ps.setLong(1, outgoing + 1);
@@ -918,12 +926,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return outgoing; return outgoing;
} else { } else {
// No connection window row exists - create one // No connection row exists - create one
rs.close(); rs.close();
ps.close(); ps.close();
sql = "INSERT INTO connectionWindows" sql = "INSERT INTO connections (contactId, index, outgoing)"
+ " (contactId, index, centre, bitmap, outgoing)" + " VALUES(?, ?, ZERO())";
+ " VALUES(?, ?, ZERO(), ZERO(), ZERO())";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, i.getInt()); ps.setInt(2, i.getInt());
@@ -944,23 +951,19 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT centre, bitmap FROM connectionWindows" String sql = "SELECT unseen FROM connectionWindows"
+ " WHERE contactId = ? AND index = ?"; + " WHERE contactId = ? AND index = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, i.getInt()); ps.setInt(2, i.getInt());
rs = ps.executeQuery(); rs = ps.executeQuery();
long centre = 0L; Collection<Long> unseen = new ArrayList<Long>();
int bitmap = 0; while(rs.next()) unseen.add(rs.getLong(1));
if(rs.next()) {
centre = rs.getLong(1);
bitmap = rs.getInt(2);
if(rs.next()) throw new DbStateException();
}
rs.close(); rs.close();
ps.close(); ps.close();
return connectionWindowFactory.createConnectionWindow(centre, if(unseen.isEmpty())
bitmap); return connectionWindowFactory.createConnectionWindow();
else return connectionWindowFactory.createConnectionWindow(unseen);
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(rs); tryToClose(rs);
tryToClose(ps); tryToClose(ps);
@@ -2154,46 +2157,34 @@ abstract class JdbcDatabase implements Database<Connection> {
public void setConnectionWindow(Connection txn, ContactId c, public void setConnectionWindow(Connection txn, ContactId c,
TransportIndex i, ConnectionWindow w) throws DbException { TransportIndex i, ConnectionWindow w) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null;
try { try {
String sql = "SELECT NULL FROM connectionWindows" // Delete any existing connection window
String sql = "DELETE FROM connectionWindows"
+ " WHERE contactId = ? AND index = ?"; + " WHERE contactId = ? AND index = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, i.getInt()); ps.setInt(2, i.getInt());
rs = ps.executeQuery(); ps.executeUpdate();
boolean found = rs.next();
if(rs.next()) throw new DbStateException();
rs.close();
ps.close(); ps.close();
if(found) { // Store the new connection window
// A connection window row exists - update it sql = "INSERT INTO connectionWindows (contactId, index, unseen)"
sql = "UPDATE connectionWindows SET centre = ?, bitmap = ?" + " VALUES(?, ?, ?)";
+ " WHERE contactId = ? AND index = ?"; ps = txn.prepareStatement(sql);
ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt());
ps.setLong(1, w.getCentre()); ps.setInt(2, i.getInt());
ps.setInt(2, w.getBitmap()); Collection<Long> unseen = w.getUnseen();
ps.setInt(3, c.getInt()); for(long l : unseen) {
ps.setInt(4, i.getInt()); ps.setLong(3, l);
int affected = ps.executeUpdate(); ps.addBatch();
if(affected != 1) throw new DbStateException();
ps.close();
} else {
// No connection window row exists - create one
sql = "INSERT INTO connectionWindows"
+ " (contactId, index, centre, bitmap, outgoing)"
+ " VALUES(?, ?, ?, ?, ZERO())";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, i.getInt());
ps.setLong(3, w.getCentre());
ps.setInt(4, w.getBitmap());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
} }
int[] affectedBatch = ps.executeBatch();
if(affectedBatch.length != unseen.size())
throw new DbStateException();
for(int j = 0; j < affectedBatch.length; j++) {
if(affectedBatch[j] != 1) throw new DbStateException();
}
ps.close();
} catch(SQLException e) { } catch(SQLException e) {
tryToClose(rs);
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
} }

View File

@@ -104,7 +104,7 @@ class ReaderImpl implements Reader {
objectReaders.length); objectReaders.length);
objectReaders = newObjectReaders; objectReaders = newObjectReaders;
} }
objectReaders[id] = o; objectReaders[id] = o;
} }
public void removeObjectReader(int id) { public void removeObjectReader(int id) {

View File

@@ -88,7 +88,7 @@ DatabaseListener {
private synchronized void calculateIvs(ContactId c, TransportId t, private synchronized void calculateIvs(ContactId c, TransportId t,
TransportIndex i, SecretKey ivKey, ConnectionWindow w) TransportIndex i, SecretKey ivKey, ConnectionWindow w)
throws DbException { throws DbException {
for(Long unseen : w.getUnseenConnectionNumbers()) { for(Long unseen : w.getUnseen()) {
Bytes iv = new Bytes(encryptIv(i, unseen, ivKey)); Bytes iv = new Bytes(encryptIv(i, unseen, ivKey));
expected.put(iv, new ConnectionContextImpl(c, t, i, unseen)); expected.put(iv, new ConnectionContextImpl(c, t, i, unseen));
} }

View File

@@ -1,11 +1,17 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import java.util.Collection;
import net.sf.briar.api.transport.ConnectionWindow; import net.sf.briar.api.transport.ConnectionWindow;
import net.sf.briar.api.transport.ConnectionWindowFactory; import net.sf.briar.api.transport.ConnectionWindowFactory;
class ConnectionWindowFactoryImpl implements ConnectionWindowFactory { class ConnectionWindowFactoryImpl implements ConnectionWindowFactory {
public ConnectionWindow createConnectionWindow(long centre, int bitmap) { public ConnectionWindow createConnectionWindow() {
return new ConnectionWindowImpl(centre, bitmap); return new ConnectionWindowImpl();
}
public ConnectionWindow createConnectionWindow(Collection<Long> unseen) {
return new ConnectionWindowImpl(unseen);
} }
} }

View File

@@ -1,69 +1,74 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static net.sf.briar.api.protocol.ProtocolConstants.CONNECTION_WINDOW_SIZE;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;
import net.sf.briar.api.transport.ConnectionWindow; import net.sf.briar.api.transport.ConnectionWindow;
import net.sf.briar.util.ByteUtils;
class ConnectionWindowImpl implements ConnectionWindow { class ConnectionWindowImpl implements ConnectionWindow {
private final Set<Long> unseen;
private long centre; private long centre;
private int bitmap;
ConnectionWindowImpl(long centre, int bitmap) { ConnectionWindowImpl() {
this.centre = centre; unseen = new TreeSet<Long>();
this.bitmap = bitmap; for(long l = 0; l < CONNECTION_WINDOW_SIZE / 2; l++) unseen.add(l);
centre = 0;
} }
public long getCentre() { ConnectionWindowImpl(Collection<Long> unseen) {
return centre; long min = Long.MAX_VALUE, max = Long.MIN_VALUE;
} for(long l : unseen) {
if(l < 0 || l > ByteUtils.MAX_32_BIT_UNSIGNED)
public int getBitmap() { throw new IllegalArgumentException();
return bitmap; if(l < min) min = l;
if(l > max) max = l;
}
if(max - min > CONNECTION_WINDOW_SIZE)
throw new IllegalArgumentException();
this.unseen = new TreeSet<Long>(unseen);
centre = max - CONNECTION_WINDOW_SIZE / 2 + 1;
for(long l = centre; l <= max; l++) {
if(!this.unseen.contains(l)) throw new IllegalArgumentException();
}
} }
public boolean isSeen(long connection) { public boolean isSeen(long connection) {
int offset = getOffset(connection); return !unseen.contains(connection);
int mask = 0x80000000 >>> offset;
return (bitmap & mask) != 0;
}
private int getOffset(long connection) {
if(connection < 0L) throw new IllegalArgumentException();
if(connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
int offset = (int) (connection - centre) + 16;
if(offset < 0 || offset > 31) throw new IllegalArgumentException();
return offset;
} }
public void setSeen(long connection) { public void setSeen(long connection) {
int offset = getOffset(connection); long bottom = getBottom(centre);
int mask = 0x80000000 >>> offset; long top = getTop(centre);
if((bitmap & mask) != 0) throw new IllegalArgumentException(); if(connection < bottom || connection > top)
bitmap |= mask; throw new IllegalArgumentException();
// If the new connection number is above the centre, slide the window if(!unseen.remove(connection)) throw new IllegalArgumentException();
if(connection >= centre) { if(connection >= centre) {
centre = connection + 1; centre = connection + 1;
bitmap <<= offset - 16 + 1; long newBottom = getBottom(centre);
long newTop = getTop(centre);
for(long l = bottom; l < newBottom; l++) unseen.remove(l);
for(long l = top + 1; l <= newTop; l++) unseen.add(l);
} }
} }
public Collection<Long> getUnseenConnectionNumbers() { // Returns the lowest value contained in a window with the given centre
Collection<Long> unseen = new ArrayList<Long>(); private long getBottom(long centre) {
for(int i = 0; i < 32; i++) { return Math.max(0, centre - CONNECTION_WINDOW_SIZE / 2);
int mask = 0x80000000 >>> i; }
if((bitmap & mask) == 0) {
long c = centre - 16 + i; // Returns the highest value contained in a window with the given centre
if(c >= 0L && c <= MAX_32_BIT_UNSIGNED) unseen.add(c); private long getTop(long centre) {
} return Math.min(ByteUtils.MAX_32_BIT_UNSIGNED,
} centre + CONNECTION_WINDOW_SIZE / 2 - 1);
// The centre of the window should be an unseen value unless the }
// maximum possible value has been seen
assert unseen.contains(centre) || centre == MAX_32_BIT_UNSIGNED + 1; public Collection<Long> getUnseen() {
return unseen; return unseen;
} }
} }

View File

@@ -60,7 +60,7 @@ public class BasicH2Test extends TestCase {
// Check that the name can be retrieved using the unique ID // Check that the name can be retrieved using the unique ID
assertEquals("foo", getName(uniqueId)); assertEquals("foo", getName(uniqueId));
} }
private void addRow(byte[] uniqueId, String name) throws SQLException { private void addRow(byte[] uniqueId, String name) throws SQLException {
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)"; String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
PreparedStatement ps = null; PreparedStatement ps = null;

View File

@@ -33,6 +33,7 @@ import net.sf.briar.api.protocol.GroupFactory;
import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.api.protocol.ProtocolConstants;
import net.sf.briar.api.protocol.Transport; import net.sf.briar.api.protocol.Transport;
import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportId;
import net.sf.briar.api.protocol.TransportIndex; import net.sf.briar.api.protocol.TransportIndex;
@@ -1446,8 +1447,8 @@ public class H2DatabaseTest extends TestCase {
remoteIndex); remoteIndex);
// The connection window should exist and be in the initial state // The connection window should exist and be in the initial state
assertNotNull(w); assertNotNull(w);
assertEquals(0L, w.getCentre()); long top = ProtocolConstants.CONNECTION_WINDOW_SIZE / 2 - 1;
assertEquals(0, w.getBitmap()); for(long l = 0; l <= top; l++) assertFalse(w.isSeen(l));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -1465,17 +1466,21 @@ public class H2DatabaseTest extends TestCase {
remoteIndex); remoteIndex);
// The connection window should exist and be in the initial state // The connection window should exist and be in the initial state
assertNotNull(w); assertNotNull(w);
assertEquals(0L, w.getCentre()); Collection<Long> unseen = w.getUnseen();
assertEquals(0, w.getBitmap()); long top = ProtocolConstants.CONNECTION_WINDOW_SIZE / 2 - 1;
assertEquals(top + 1, unseen.size());
for(long l = 0; l <= top; l++) {
assertFalse(w.isSeen(l));
assertTrue(unseen.contains(l));
}
// Update the connection window and store it // Update the connection window and store it
w.setSeen(5L); w.setSeen(5);
db.setConnectionWindow(txn, contactId, remoteIndex, w); db.setConnectionWindow(txn, contactId, remoteIndex, w);
// Check that the connection window was stored // Check that the connection window was stored
w = db.getConnectionWindow(txn, contactId, remoteIndex); w = db.getConnectionWindow(txn, contactId, remoteIndex);
assertNotNull(w); assertNotNull(w);
assertEquals(6L, w.getCentre()); top += 5;
assertTrue(w.isSeen(5L)); for(long l = 0; l <= top; l++) assertEquals(l == 5, w.isSeen(l));
assertEquals(0x00010000, w.getBitmap());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();

View File

@@ -28,7 +28,7 @@ public class ConsumersTest extends TestCase {
Injector i = Guice.createInjector(new CryptoModule()); Injector i = Guice.createInjector(new CryptoModule());
crypto = i.getInstance(CryptoComponent.class); crypto = i.getInstance(CryptoComponent.class);
} }
@Test @Test
public void testDigestingConsumer() throws Exception { public void testDigestingConsumer() throws Exception {
MessageDigest messageDigest = crypto.getMessageDigest(); MessageDigest messageDigest = crypto.getMessageDigest();

View File

@@ -101,7 +101,7 @@ public class WriterImplTest extends TestCase {
@Test @Test
public void testWriteFloat32() throws IOException { public void testWriteFloat32() throws IOException {
// http://babbage.cs.qc.edu/IEEE-754/Decimal.html // http://babbage.cs.qc.edu/IEEE-754/Decimal.html
// 1 bit for sign, 8 for exponent, 23 for significand // 1 bit for sign, 8 for exponent, 23 for significand
w.writeFloat32(0F); // 0 0 0 -> 0x00000000 w.writeFloat32(0F); // 0 0 0 -> 0x00000000
w.writeFloat32(1F); // 0 127 1 -> 0x3F800000 w.writeFloat32(1F); // 0 127 1 -> 0x3F800000
w.writeFloat32(2F); // 0 128 1 -> 0x40000000 w.writeFloat32(2F); // 0 128 1 -> 0x40000000
@@ -118,7 +118,7 @@ public class WriterImplTest extends TestCase {
@Test @Test
public void testWriteFloat64() throws IOException { public void testWriteFloat64() throws IOException {
// 1 bit for sign, 11 for exponent, 52 for significand // 1 bit for sign, 11 for exponent, 52 for significand
w.writeFloat64(0.0); // 0 0 0 -> 0x0000000000000000 w.writeFloat64(0.0); // 0 0 0 -> 0x0000000000000000
w.writeFloat64(1.0); // 0 1023 1 -> 0x3FF0000000000000 w.writeFloat64(1.0); // 0 1023 1 -> 0x3FF0000000000000
w.writeFloat64(2.0); // 0 1024 1 -> 0x4000000000000000 w.writeFloat64(2.0); // 0 1024 1 -> 0x4000000000000000

View File

@@ -84,7 +84,7 @@ public class ConnectionDecrypterImplTest extends TestCase {
out.write(ciphertextMac); out.write(ciphertextMac);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Use a ConnectionDecrypter to decrypt the ciphertext // Use a ConnectionDecrypter to decrypt the ciphertext
ConnectionDecrypter d = new ConnectionDecrypterImpl(in, ConnectionDecrypter d = new ConnectionDecrypterImpl(in,
IvEncoder.encodeIv(initiator, transportIndex, connection), IvEncoder.encodeIv(initiator, transportIndex, connection),
frameCipher, frameKey); frameCipher, frameKey);
// First frame // First frame

View File

@@ -49,7 +49,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
Transport transport = new Transport(transportId, localIndex, Transport transport = new Transport(transportId, localIndex,
Collections.singletonMap("foo", "bar")); Collections.singletonMap("foo", "bar"));
transports = Collections.singletonList(transport); transports = Collections.singletonList(transport);
connectionWindow = new ConnectionWindowImpl(0L, 0); connectionWindow = new ConnectionWindowImpl();
} }
@Test @Test
@@ -120,8 +120,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
// Second time - the IV should no longer be expected // Second time - the IV should no longer be expected
assertNull(c.acceptConnection(encryptedIv)); assertNull(c.acceptConnection(encryptedIv));
// The window should have advanced // The window should have advanced
assertEquals(4L, connectionWindow.getCentre()); Collection<Long> unseen = connectionWindow.getUnseen();
Collection<Long> unseen = connectionWindow.getUnseenConnectionNumbers();
assertEquals(19, unseen.size()); assertEquals(19, unseen.size());
for(int i = 0; i < 19; i++) { for(int i = 0; i < 19; i++) {
if(i == 3) continue; if(i == 3) continue;

View File

@@ -1,18 +1,18 @@
package net.sf.briar.transport; package net.sf.briar.transport;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import junit.framework.TestCase; import junit.framework.TestCase;
import net.sf.briar.util.ByteUtils;
import org.junit.Test; import org.junit.Test;
public class ConnectionWindowImplTest extends TestCase { public class ConnectionWindowImplTest extends TestCase {
private static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
@Test @Test
public void testWindowSliding() { public void testWindowSliding() {
ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); ConnectionWindowImpl w = new ConnectionWindowImpl();
for(int i = 0; i < 100; i++) { for(int i = 0; i < 100; i++) {
assertFalse(w.isSeen(i)); assertFalse(w.isSeen(i));
w.setSeen(i); w.setSeen(i);
@@ -22,7 +22,7 @@ public class ConnectionWindowImplTest extends TestCase {
@Test @Test
public void testWindowJumping() { public void testWindowJumping() {
ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); ConnectionWindowImpl w = new ConnectionWindowImpl();
for(int i = 0; i < 100; i += 13) { for(int i = 0; i < 100; i += 13) {
assertFalse(w.isSeen(i)); assertFalse(w.isSeen(i));
w.setSeen(i); w.setSeen(i);
@@ -32,7 +32,7 @@ public class ConnectionWindowImplTest extends TestCase {
@Test @Test
public void testWindowUpperLimit() { public void testWindowUpperLimit() {
ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); ConnectionWindowImpl w = new ConnectionWindowImpl();
// Centre is 0, highest value in window is 15 // Centre is 0, highest value in window is 15
w.setSeen(15); w.setSeen(15);
// Centre is 16, highest value in window is 31 // Centre is 16, highest value in window is 31
@@ -42,18 +42,22 @@ public class ConnectionWindowImplTest extends TestCase {
w.setSeen(48); w.setSeen(48);
fail(); fail();
} catch(IllegalArgumentException expected) {} } catch(IllegalArgumentException expected) {}
w = new ConnectionWindowImpl(MAX_32_BIT_UNSIGNED - 1, 0); // Values greater than 2^32 - 1 should never be allowed
// Values greater than 2^31 - 1 should never be allowed Collection<Long> unseen = new ArrayList<Long>();
w.setSeen(MAX_32_BIT_UNSIGNED); for(int i = 0; i < 32; i++) {
unseen.add(ByteUtils.MAX_32_BIT_UNSIGNED - i);
}
w = new ConnectionWindowImpl(unseen);
w.setSeen(ByteUtils.MAX_32_BIT_UNSIGNED);
try { try {
w.setSeen(MAX_32_BIT_UNSIGNED + 1); w.setSeen(ByteUtils.MAX_32_BIT_UNSIGNED + 1);
fail(); fail();
} catch(IllegalArgumentException expected) {} } catch(IllegalArgumentException expected) {}
} }
@Test @Test
public void testWindowLowerLimit() { public void testWindowLowerLimit() {
ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); ConnectionWindowImpl w = new ConnectionWindowImpl();
// Centre is 0, negative values should never be allowed // Centre is 0, negative values should never be allowed
try { try {
w.setSeen(-1); w.setSeen(-1);
@@ -83,7 +87,7 @@ public class ConnectionWindowImplTest extends TestCase {
@Test @Test
public void testCannotSetSeenTwice() { public void testCannotSetSeenTwice() {
ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); ConnectionWindowImpl w = new ConnectionWindowImpl();
w.setSeen(15); w.setSeen(15);
try { try {
w.setSeen(15); w.setSeen(15);
@@ -93,9 +97,9 @@ public class ConnectionWindowImplTest extends TestCase {
@Test @Test
public void testGetUnseenConnectionNumbers() { public void testGetUnseenConnectionNumbers() {
ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); ConnectionWindowImpl w = new ConnectionWindowImpl();
// Centre is 0; window should cover 0 to 15, inclusive, with none seen // Centre is 0; window should cover 0 to 15, inclusive, with none seen
Collection<Long> unseen = w.getUnseenConnectionNumbers(); Collection<Long> unseen = w.getUnseen();
assertEquals(16, unseen.size()); assertEquals(16, unseen.size());
for(int i = 0; i < 16; i++) { for(int i = 0; i < 16; i++) {
assertTrue(unseen.contains(Long.valueOf(i))); assertTrue(unseen.contains(Long.valueOf(i)));
@@ -104,7 +108,7 @@ public class ConnectionWindowImplTest extends TestCase {
w.setSeen(3); w.setSeen(3);
w.setSeen(4); w.setSeen(4);
// Centre is 5; window should cover 0 to 20, inclusive, with two seen // Centre is 5; window should cover 0 to 20, inclusive, with two seen
unseen = w.getUnseenConnectionNumbers(); unseen = w.getUnseen();
assertEquals(19, unseen.size()); assertEquals(19, unseen.size());
for(int i = 0; i < 21; i++) { for(int i = 0; i < 21; i++) {
if(i == 3 || i == 4) { if(i == 3 || i == 4) {
@@ -117,7 +121,7 @@ public class ConnectionWindowImplTest extends TestCase {
} }
w.setSeen(19); w.setSeen(19);
// Centre is 20; window should cover 4 to 35, inclusive, with two seen // Centre is 20; window should cover 4 to 35, inclusive, with two seen
unseen = w.getUnseenConnectionNumbers(); unseen = w.getUnseen();
assertEquals(30, unseen.size()); assertEquals(30, unseen.size());
for(int i = 4; i < 36; i++) { for(int i = 4; i < 36; i++) {
if(i == 4 || i == 19) { if(i == 4 || i == 19) {