diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 2ef2b4d96..03188adeb 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 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 3ec801404..511f65cf5 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 425438081..4f73cb9eb 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; *
  • ratings *
  • subscriptions *
  • transports + *
  • windows * */ interface Database { @@ -159,8 +160,10 @@ interface Database { /** * Returns the connection reordering window for the given contact and * transport. + *

    + * 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 { /** * Sets the connection reordering window for the given contact and * transport. + *

    + * 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 5002ca4fd..f2b314d68 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 { } 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 { + " 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 { } } - 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 { + " 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 { + " 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 fbb44cd36..6a80b926c 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 extends DatabaseComponentImpl { new ReentrantReadWriteLock(true); private final ReentrantReadWriteLock transportLock = new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock windowLock = + new ReentrantReadWriteLock(true); @Inject ReadWriteLockDatabaseComponent(Database db, DatabaseCleaner cleaner) { @@ -488,6 +491,31 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } } + 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 getContacts() throws DbException { contactLock.readLock().lock(); try { @@ -869,6 +897,28 @@ class ReadWriteLockDatabaseComponent extends DatabaseComponentImpl { } } + 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 d8680331b..0bfc7db14 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 extends DatabaseComponentImpl { 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 db, DatabaseCleaner cleaner) { @@ -373,6 +375,25 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } + 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 getContacts() throws DbException { synchronized(contactLock) { Txn txn = db.startTransaction(); @@ -659,6 +680,22 @@ class SynchronizedDatabaseComponent extends DatabaseComponentImpl { } } + 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 dc6d5615d..33df74c05 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> 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 f12acc766..4a8bd4ee4 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 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 c95dc19ed..54595b234 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