From 07b34cfbab867621b1a41ec84e9c4867a5f43736 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Thu, 11 Aug 2011 15:19:32 +0100
Subject: [PATCH] Added a lock for the connectionWindows table and exposed
 getConnectionWindow() and setConnectionWindow() through the DatabaseComponent
 interface.

---
 .../sf/briar/api/db/DatabaseComponent.java    | 15 ++++++
 .../api/transport/ConnectionRecogniser.java   |  3 +-
 components/net/sf/briar/db/Database.java      |  9 +++-
 components/net/sf/briar/db/JdbcDatabase.java  | 12 ++---
 .../db/ReadWriteLockDatabaseComponent.java    | 50 +++++++++++++++++++
 .../db/SynchronizedDatabaseComponent.java     | 37 ++++++++++++++
 .../sf/briar/invitation/InvitationWorker.java |  9 ++--
 .../sf/briar/db/DatabaseComponentTest.java    | 33 ++++++++++--
 .../transport/ConnectionWindowImplTest.java   |  9 ++++
 9 files changed, 159 insertions(+), 18 deletions(-)

diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index 2ef2b4d96a..03188adeb6 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 3ec801404f..511f65cf57 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 425438081a..4f73cb9eb7 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 5002ca4fd1..f2b314d68d 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 fbb44cd362..6a80b926c0 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 d8680331bd..0bfc7db141 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 dc6d5615d4..33df74c059 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 f12acc7666..4a8bd4ee46 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 c95dc19edd..54595b2342 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
-- 
GitLab