diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 2ef2b4d96a0ead8fd8e340f43ee8d188571869b2..03188adeb61514e459125b8b6d34fb75ea576a11 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -22,6 +22,7 @@ import net.sf.briar.api.protocol.writers.OfferWriter; import net.sf.briar.api.protocol.writers.RequestWriter; import net.sf.briar.api.protocol.writers.SubscriptionWriter; import net.sf.briar.api.protocol.writers.TransportWriter; +import net.sf.briar.api.transport.ConnectionWindow; /** * Encapsulates the database implementation and exposes high-level operations @@ -103,6 +104,13 @@ public interface DatabaseComponent { void generateTransportUpdate(ContactId c, TransportWriter t) throws DbException, IOException; + /** + * Returns the connection reordering window for the given contact and + * transport. + */ + ConnectionWindow getConnectionWindow(ContactId c, int transportId) + throws DbException; + /** Returns the IDs of all contacts. */ Collection<ContactId> getContacts() throws DbException; @@ -156,6 +164,13 @@ public interface DatabaseComponent { /** Removes a contact (and all associated state) from the database. */ void removeContact(ContactId c) throws DbException; + /** + * Sets the connection reordering window for the given contact and + * transport. + */ + void setConnectionWindow(ContactId c, int transportId, ConnectionWindow w) + throws DbException; + /** Records the user's rating for the given author. */ void setRating(AuthorId a, Rating r) throws DbException; diff --git a/api/net/sf/briar/api/transport/ConnectionRecogniser.java b/api/net/sf/briar/api/transport/ConnectionRecogniser.java index 3ec801404f7fd45040f2018999f62556e1fd4fa7..511f65cf573191d3cf77fcc5059d7b987d318236 100644 --- a/api/net/sf/briar/api/transport/ConnectionRecogniser.java +++ b/api/net/sf/briar/api/transport/ConnectionRecogniser.java @@ -1,6 +1,7 @@ package net.sf.briar.api.transport; import net.sf.briar.api.ContactId; +import net.sf.briar.api.db.DbException; /** * Maintains a transport plugin's connection reordering window and decides @@ -12,5 +13,5 @@ public interface ConnectionRecogniser { * Returns the ID of the contact who created the tag if the connection * should be accepted, or null if the connection should be rejected. */ - ContactId acceptConnection(byte[] tag); + ContactId acceptConnection(byte[] tag) throws DbException; } diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 425438081ab701b5d49c4ca235209c11c9f05c55..4f73cb9eb700930e44072707f17698405216fbe5 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -31,6 +31,7 @@ import net.sf.briar.api.transport.ConnectionWindow; * <li> ratings * <li> subscriptions * <li> transports + * <li> windows * </ul> */ interface Database<T> { @@ -159,8 +160,10 @@ interface Database<T> { /** * Returns the connection reordering window for the given contact and * transport. + * <p> + * Locking: contacts read, windows read. */ - ConnectionWindow getConnectionWindow(T txn, ContactId c, int transport) + ConnectionWindow getConnectionWindow(T txn, ContactId c, int transportId) throws DbException; /** @@ -382,8 +385,10 @@ interface Database<T> { /** * Sets the connection reordering window for the given contact and * transport. + * <p> + * Locking: contacts read, windows write. */ - void setConnectionWindow(T txn, ContactId c, int transport, + void setConnectionWindow(T txn, ContactId c, int transportId, ConnectionWindow w) throws DbException; /** diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index 5002ca4fd1f15056598384e241e60fdf83c6a915..f2b314d68d643584fa7dc8d9461997f625a82246 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -734,7 +734,7 @@ abstract class JdbcDatabase implements Database<Connection> { } public ConnectionWindow getConnectionWindow(Connection txn, ContactId c, - int transport) throws DbException { + int transportId) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -742,7 +742,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " WHERE contactId = ? AND transportId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setInt(2, transport); + ps.setInt(2, transportId); rs = ps.executeQuery(); long centre = 0L; int bitmap = 0; @@ -1528,8 +1528,8 @@ abstract class JdbcDatabase implements Database<Connection> { } } - public void setConnectionWindow(Connection txn, ContactId c, int transport, - ConnectionWindow w) throws DbException { + public void setConnectionWindow(Connection txn, ContactId c, + int transportId, ConnectionWindow w) throws DbException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -1537,7 +1537,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " WHERE contactId = ? AND transportId = ?"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setInt(2, transport); + ps.setInt(2, transportId); rs = ps.executeQuery(); if(rs.next()) { if(rs.next()) throw new DbStateException(); @@ -1556,7 +1556,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " VALUES(?, ?, ?, ?)"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); - ps.setInt(2, transport); + ps.setInt(2, transportId); ps.setLong(3, w.getCentre()); ps.setInt(4, w.getBitmap()); int affected = ps.executeUpdate(); diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java index fbb44cd362ee6712886e7db3736a5696246a1409..6a80b926c0d26b3224c3f0d1d9d9a87bee712e46 100644 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java @@ -32,6 +32,7 @@ import net.sf.briar.api.protocol.writers.OfferWriter; import net.sf.briar.api.protocol.writers.RequestWriter; import net.sf.briar.api.protocol.writers.SubscriptionWriter; import net.sf.briar.api.protocol.writers.TransportWriter; +import net.sf.briar.api.transport.ConnectionWindow; import com.google.inject.Inject; @@ -61,6 +62,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock transportLock = new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock windowLock = + new ReentrantReadWriteLock(true); @Inject ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner) { @@ -488,6 +491,31 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } + public ConnectionWindow getConnectionWindow(ContactId c, int transportId) + throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + windowLock.readLock().lock(); + try { + Txn txn = db.startTransaction(); + try { + ConnectionWindow w = + db.getConnectionWindow(txn, c, transportId); + db.commitTransaction(txn); + return w; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + public Collection<ContactId> getContacts() throws DbException { contactLock.readLock().lock(); try { @@ -869,6 +897,28 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } + public void setConnectionWindow(ContactId c, int transportId, + ConnectionWindow w) throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + windowLock.writeLock().lock(); + try { + Txn txn = db.startTransaction(); + try { + db.setConnectionWindow(txn, c, transportId, w); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + public void setRating(AuthorId a, Rating r) throws DbException { messageLock.writeLock().lock(); try { diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java index d8680331bdcbe93f096f7f5ebb32824521e23bab..0bfc7db14192e828b9c4bf12112974c587808b6a 100644 --- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java +++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java @@ -31,6 +31,7 @@ import net.sf.briar.api.protocol.writers.OfferWriter; import net.sf.briar.api.protocol.writers.RequestWriter; import net.sf.briar.api.protocol.writers.SubscriptionWriter; import net.sf.briar.api.protocol.writers.TransportWriter; +import net.sf.briar.api.transport.ConnectionWindow; import com.google.inject.Inject; @@ -54,6 +55,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { private final Object ratingLock = new Object(); private final Object subscriptionLock = new Object(); private final Object transportLock = new Object(); + private final Object windowLock = new Object(); @Inject SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner) { @@ -373,6 +375,25 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } + public ConnectionWindow getConnectionWindow(ContactId c, int transportId) + throws DbException { + synchronized(contactLock) { + if(!containsContact(c)) throw new NoSuchContactException(); + synchronized(windowLock) { + Txn txn = db.startTransaction(); + try { + ConnectionWindow w = + db.getConnectionWindow(txn, c, transportId); + db.commitTransaction(txn); + return w; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } + } + } + public Collection<ContactId> getContacts() throws DbException { synchronized(contactLock) { Txn txn = db.startTransaction(); @@ -659,6 +680,22 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> { } } + public void setConnectionWindow(ContactId c, int transportId, + ConnectionWindow w) throws DbException { + synchronized(contactLock) { + if(!containsContact(c)) throw new NoSuchContactException(); + synchronized(windowLock) { + Txn txn = db.startTransaction(); + try { + db.setConnectionWindow(txn, c, transportId, w); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + } + } + } + } + public void setRating(AuthorId a, Rating r) throws DbException { synchronized(messageLock) { synchronized(ratingLock) { diff --git a/components/net/sf/briar/invitation/InvitationWorker.java b/components/net/sf/briar/invitation/InvitationWorker.java index dc6d5615d4d7b348d5001069d1a18048e7315b6d..33df74c059b4e740a8c5f91cca82d2208a24ce93 100644 --- a/components/net/sf/briar/invitation/InvitationWorker.java +++ b/components/net/sf/briar/invitation/InvitationWorker.java @@ -20,16 +20,15 @@ class InvitationWorker implements Runnable { private final InvitationCallback callback; private final InvitationParameters parameters; - private final DatabaseComponent databaseComponent; + private final DatabaseComponent db; private final WriterFactory writerFactory; InvitationWorker(final InvitationCallback callback, - InvitationParameters parameters, - DatabaseComponent databaseComponent, + InvitationParameters parameters, DatabaseComponent db, WriterFactory writerFactory) { this.callback = callback; this.parameters = parameters; - this.databaseComponent = databaseComponent; + this.db = db; this.writerFactory = writerFactory; } @@ -73,7 +72,7 @@ class InvitationWorker implements Runnable { // FIXME: Create a real invitation Map<String, Map<String, String>> transports; try { - transports = databaseComponent.getTransports(); + transports = db.getTransports(); } catch(DbException e) { throw new IOException(e.getMessage()); } diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index f12acc766676675fffed331c7a49e73be49f4ac9..4a8bd4ee4684b71ae16796b7500bbdb5ef44a26b 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -11,8 +11,8 @@ import net.sf.briar.TestUtils; import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; import net.sf.briar.api.db.DatabaseComponent; -import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DatabaseListener; +import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.Status; import net.sf.briar.api.protocol.Ack; @@ -32,6 +32,7 @@ import net.sf.briar.api.protocol.writers.OfferWriter; import net.sf.briar.api.protocol.writers.RequestWriter; import net.sf.briar.api.protocol.writers.SubscriptionWriter; import net.sf.briar.api.protocol.writers.TransportWriter; +import net.sf.briar.api.transport.ConnectionWindow; import org.jmock.Expectations; import org.jmock.Mockery; @@ -81,6 +82,8 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ConnectionWindow connectionWindow = + context.mock(ConnectionWindow.class); final Group group = context.mock(Group.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ @@ -99,6 +102,11 @@ public abstract class DatabaseComponentTest extends TestCase { // getContacts() oneOf(database).getContacts(txn); will(returnValue(Collections.singletonList(contactId))); + // getConnectionWindow(contactId, 123) + oneOf(database).containsContact(txn, contactId); + will(returnValue(true)); + oneOf(database).getConnectionWindow(txn, contactId, 123); + will(returnValue(connectionWindow)); // getTransports(contactId) oneOf(database).containsContact(txn, contactId); will(returnValue(true)); @@ -129,6 +137,11 @@ public abstract class DatabaseComponentTest extends TestCase { // unsubscribe(groupId) again oneOf(database).containsSubscription(txn, groupId); will(returnValue(false)); + // setConnectionWindow(contactId, 123, connectionWindow) + oneOf(database).containsContact(txn, contactId); + will(returnValue(true)); + oneOf(database).setConnectionWindow(txn, contactId, 123, + connectionWindow); // removeContact(contactId) oneOf(database).removeContact(txn, contactId); // close() @@ -142,12 +155,14 @@ public abstract class DatabaseComponentTest extends TestCase { assertEquals(Rating.UNRATED, db.getRating(authorId)); assertEquals(contactId, db.addContact(transports)); assertEquals(Collections.singletonList(contactId), db.getContacts()); + assertEquals(connectionWindow, db.getConnectionWindow(contactId, 123)); assertEquals(transports, db.getTransports(contactId)); db.subscribe(group); db.subscribe(group); // Again - check listeners aren't called assertEquals(Collections.singletonList(groupId), db.getSubscriptions()); db.unsubscribe(groupId); db.unsubscribe(groupId); // Again - check listeners aren't called + db.setConnectionWindow(contactId, 123, connectionWindow); db.removeContact(contactId); db.removeListener(listener); db.close(); @@ -472,11 +487,11 @@ public abstract class DatabaseComponentTest extends TestCase { context.checking(new Expectations() {{ // Check whether the contact is still in the DB (which it's not) // once for each method - exactly(14).of(database).startTransaction(); + exactly(16).of(database).startTransaction(); will(returnValue(txn)); - exactly(14).of(database).containsContact(txn, contactId); + exactly(16).of(database).containsContact(txn, contactId); will(returnValue(false)); - exactly(14).of(database).commitTransaction(txn); + exactly(16).of(database).commitTransaction(txn); }}); DatabaseComponent db = createDatabaseComponent(database, cleaner); @@ -516,6 +531,11 @@ public abstract class DatabaseComponentTest extends TestCase { fail(); } catch(NoSuchContactException expected) {} + try { + db.getConnectionWindow(contactId, 123); + fail(); + } catch(NoSuchContactException expected) {} + try { db.getTransports(contactId); fail(); @@ -551,6 +571,11 @@ public abstract class DatabaseComponentTest extends TestCase { fail(); } catch(NoSuchContactException expected) {} + try { + db.setConnectionWindow(contactId, 123, null); + fail(); + } catch(NoSuchContactException expected) {} + context.assertIsSatisfied(); } diff --git a/test/net/sf/briar/transport/ConnectionWindowImplTest.java b/test/net/sf/briar/transport/ConnectionWindowImplTest.java index c95dc19eddebb4cd2e5a3544095bbe544dabe189..54595b2342b66152afdae866daf4ab3071096baf 100644 --- a/test/net/sf/briar/transport/ConnectionWindowImplTest.java +++ b/test/net/sf/briar/transport/ConnectionWindowImplTest.java @@ -8,6 +8,8 @@ import org.junit.Test; public class ConnectionWindowImplTest extends TestCase { + private static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 + @Test public void testWindowSliding() { ConnectionWindowImpl w = new ConnectionWindowImpl(0L, 0); @@ -40,6 +42,13 @@ public class ConnectionWindowImplTest extends TestCase { w.setSeen(48); fail(); } catch(IllegalArgumentException expected) {} + w = new ConnectionWindowImpl(MAX_32_BIT_UNSIGNED - 1, 0); + // Values greater than 2^31 - 1 should never be allowed + w.setSeen(MAX_32_BIT_UNSIGNED); + try { + w.setSeen(MAX_32_BIT_UNSIGNED + 1); + fail(); + } catch(IllegalArgumentException expected) {} } @Test