From 17fe358fd9efaaa05bd7292007e950390a64092b Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Tue, 27 Mar 2018 17:01:39 +0100
Subject: [PATCH] Add a method for binding transport keys to a contact.

---
 .../bramble/api/db/DatabaseComponent.java     |  6 +++
 .../bramble/api/transport/KeyManager.java     | 10 ++++-
 .../org/briarproject/bramble/db/Database.java |  6 +++
 .../bramble/db/DatabaseComponentImpl.java     | 12 ++++++
 .../briarproject/bramble/db/JdbcDatabase.java | 28 +++++++++++++
 .../bramble/transport/KeyManagerImpl.java     | 27 ++++++++++---
 .../transport/TransportKeyManager.java        |  2 +
 .../transport/TransportKeyManagerImpl.java    | 39 ++++++++++++-------
 .../bramble/db/DatabaseComponentImplTest.java | 34 ++++++++++++----
 .../bramble/transport/KeyManagerImplTest.java |  3 +-
 .../TransportKeyManagerImplTest.java          |  4 +-
 11 files changed, 139 insertions(+), 32 deletions(-)

diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
index 45f721f22d..2a4f262de8 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseComponent.java
@@ -110,6 +110,12 @@ public interface DatabaseComponent {
 	KeySetId addTransportKeys(Transaction txn, @Nullable ContactId c,
 			TransportKeys k) throws DbException;
 
+	/**
+	 * Binds the given keys for the given transport to the given contact.
+	 */
+	void bindTransportKeys(Transaction txn, ContactId c, TransportId t,
+			KeySetId k) throws DbException;
+
 	/**
 	 * Returns true if the database contains the given contact for the given
 	 * local pseudonym.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
index ef422d66e5..50a5cf9480 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.plugin.TransportId;
 
-import java.util.Collection;
+import java.util.Map;
 
 import javax.annotation.Nullable;
 
@@ -31,9 +31,15 @@ public interface KeyManager {
 	 * Derives and stores a set of unbound transport keys for each transport
 	 * and returns the key set IDs.
 	 */
-	Collection<KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
+	Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
 			long timestamp, boolean alice) throws DbException;
 
+	/**
+	 * Binds the given transport keys to the given contact.
+	 */
+	void bindKeys(Transaction txn, ContactId c, Map<TransportId, KeySetId> keys)
+			throws DbException;
+
 	/**
 	 * Returns a {@link StreamContext} for sending a stream to the given
 	 * contact over the given transport, or null if an error occurs or the
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
index e8597eaba7..b11e1b8281 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/Database.java
@@ -131,6 +131,12 @@ interface Database<T> {
 	KeySetId addTransportKeys(T txn, @Nullable ContactId c, TransportKeys k)
 			throws DbException;
 
+	/**
+	 * Binds the given keys for the given transport to the given contact.
+	 */
+	void bindTransportKeys(T txn, ContactId c, TransportId t, KeySetId k)
+			throws DbException;
+
 	/**
 	 * Returns true if the database contains the given contact for the given
 	 * local pseudonym.
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
index f90f9e3ff1..305b672900 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java
@@ -245,6 +245,18 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		return db.addTransportKeys(txn, c, k);
 	}
 
+	@Override
+	public void bindTransportKeys(Transaction transaction, ContactId c,
+			TransportId t, KeySetId k) throws DbException {
+		if (transaction.isReadOnly()) throw new IllegalArgumentException();
+		T txn = unbox(transaction);
+		if (!db.containsContact(txn, c))
+			throw new NoSuchContactException();
+		if (!db.containsTransport(txn, t))
+			throw new NoSuchTransportException();
+		db.bindTransportKeys(txn, c, t, k);
+	}
+
 	@Override
 	public boolean containsContact(Transaction transaction, AuthorId remote,
 			AuthorId local) throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index 327203d630..747145a344 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -950,6 +950,33 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	@Override
+	public void bindTransportKeys(Connection txn, ContactId c, TransportId t,
+			KeySetId k) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE outgoingKeys SET contactId = ?"
+					+ " WHERE keySetId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setInt(2, k.getInt());
+			int affected = ps.executeUpdate();
+			if (affected < 0) throw new DbStateException();
+			ps.close();
+			sql = "UPDATE incomingKeys SET contactId = ?"
+					+ " WHERE keySetId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setInt(2, k.getInt());
+			affected = ps.executeUpdate();
+			if (affected < 0) throw new DbStateException();
+			ps.close();
+		} catch (SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	@Override
 	public boolean containsContact(Connection txn, AuthorId remote,
 			AuthorId local) throws DbException {
@@ -2882,6 +2909,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		try {
 			// Delete any existing outgoing keys - this will also remove any
 			// incoming keys with the same key set ID
+			// TODO: Add an index to speed this up?
 			String sql = "DELETE FROM outgoingKeys WHERE keySetId = ?";
 			ps = txn.prepareStatement(sql);
 			for (KeySet ks : keys) {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index 993cb5c4c6..c07c9e1e72 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
@@ -22,8 +22,6 @@ import org.briarproject.bramble.api.transport.KeyManager;
 import org.briarproject.bramble.api.transport.KeySetId;
 import org.briarproject.bramble.api.transport.StreamContext;
 
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -108,15 +106,32 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 	}
 
 	@Override
-	public Collection<KeySetId> addUnboundKeys(Transaction txn,
+	public Map<TransportId, KeySetId> addUnboundKeys(Transaction txn,
 			SecretKey master, long timestamp, boolean alice)
 			throws DbException {
-		Collection<KeySetId> ids = new ArrayList<>();
-		for (TransportKeyManager m : managers.values())
-			ids.add(m.addUnboundKeys(txn, master, timestamp, alice));
+		Map<TransportId, KeySetId> ids = new HashMap<>();
+		for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
+			TransportId t = e.getKey();
+			TransportKeyManager m = e.getValue();
+			ids.put(t, m.addUnboundKeys(txn, master, timestamp, alice));
+		}
 		return ids;
 	}
 
+	@Override
+	public void bindKeys(Transaction txn, ContactId c,
+			Map<TransportId, KeySetId> keys) throws DbException {
+		for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
+			TransportId t = e.getKey();
+			TransportKeyManager m = managers.get(t);
+			if (m == null) {
+				if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
+			} else {
+				m.bindKeys(txn, c, e.getValue());
+			}
+		}
+	}
+
 	@Override
 	public StreamContext getStreamContext(ContactId c, TransportId t)
 			throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
index 5da58633f0..41e31fbe62 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
@@ -21,6 +21,8 @@ interface TransportKeyManager {
 	KeySetId addUnboundKeys(Transaction txn, SecretKey master, long timestamp,
 			boolean alice) throws DbException;
 
+	void bindKeys(Transaction txn, ContactId c, KeySetId k) throws DbException;
+
 	void removeContact(ContactId c);
 
 	@Nullable
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
index b1dbb6ced9..6b9a1519eb 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
@@ -22,7 +22,6 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,7 +56,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	private final ReentrantLock lock = new ReentrantLock();
 
 	// The following are locking: lock
-	private final Collection<MutableKeySet> keys = new ArrayList<>();
+	private final Map<KeySetId, MutableKeySet> keys = new HashMap<>();
 	private final Map<Bytes, TagContext> inContexts = new HashMap<>();
 	private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>();
 
@@ -123,7 +122,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
 			MutableTransportKeys m) {
 		MutableKeySet ks = new MutableKeySet(keySetId, contactId, m);
-		keys.add(ks);
+		keys.put(keySetId, ks);
 		if (contactId != null) {
 			encodeTags(keySetId, contactId, m.getPreviousIncomingKeys());
 			encodeTags(keySetId, contactId, m.getCurrentIncomingKeys());
@@ -204,22 +203,34 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		}
 	}
 
+	@Override
+	public void bindKeys(Transaction txn, ContactId c, KeySetId k)
+			throws DbException {
+		lock.lock();
+		try {
+			MutableKeySet ks = keys.get(k);
+			if (ks == null) throw new IllegalArgumentException();
+			if (ks.getContactId() != null) throw new IllegalArgumentException();
+			MutableTransportKeys m = ks.getTransportKeys();
+			addKeys(k, c, m);
+			db.bindTransportKeys(txn, c, m.getTransportId(), k);
+		} finally {
+			lock.unlock();
+		}
+	}
+
 	@Override
 	public void removeContact(ContactId c) {
 		lock.lock();
 		try {
 			// Remove mutable state for the contact
-			Iterator<Entry<Bytes, TagContext>> inContextsIter =
-					inContexts.entrySet().iterator();
-			while (inContextsIter.hasNext()) {
-				ContactId c1 = inContextsIter.next().getValue().contactId;
-				if (c1.equals(c)) inContextsIter.remove();
-			}
+			Iterator<TagContext> it = inContexts.values().iterator();
+			while (it.hasNext()) if (it.next().contactId.equals(c)) it.remove();
 			outContexts.remove(c);
-			Iterator<MutableKeySet> keysIter = keys.iterator();
-			while (keysIter.hasNext()) {
-				ContactId c1 = keysIter.next().getContactId();
-				if (c1 != null && c1.equals(c)) keysIter.remove();
+			Iterator<MutableKeySet> it1 = keys.values().iterator();
+			while (it1.hasNext()) {
+				ContactId c1 = it1.next().getContactId();
+				if (c1 != null && c1.equals(c)) it1.remove();
 			}
 		} finally {
 			lock.unlock();
@@ -300,7 +311,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		try {
 			// Rotate the keys to the current rotation period
 			Collection<KeySet> snapshot = new ArrayList<>(keys.size());
-			for (MutableKeySet ks : keys) {
+			for (MutableKeySet ks : keys.values()) {
 				snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(),
 						ks.getTransportKeys().snapshot()));
 			}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index eb31247ce0..4832d9d8bc 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -284,11 +284,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			throws Exception {
 		context.checking(new Expectations() {{
 			// Check whether the contact is in the DB (which it's not)
-			exactly(17).of(database).startTransaction();
+			exactly(18).of(database).startTransaction();
 			will(returnValue(txn));
-			exactly(17).of(database).containsContact(txn, contactId);
+			exactly(18).of(database).containsContact(txn, contactId);
 			will(returnValue(false));
-			exactly(17).of(database).abortTransaction(txn);
+			exactly(18).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
@@ -303,6 +303,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			db.endTransaction(transaction);
 		}
 
+		transaction = db.startTransaction(false);
+		try {
+			db.bindTransportKeys(transaction, contactId, transportId, keySetId);
+			fail();
+		} catch (NoSuchContactException expected) {
+			// Expected
+		} finally {
+			db.endTransaction(transaction);
+		}
+
 		transaction = db.startTransaction(false);
 		try {
 			db.generateAck(transaction, contactId, 123);
@@ -768,13 +778,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			// endTransaction()
 			oneOf(database).commitTransaction(txn);
 			// Check whether the transport is in the DB (which it's not)
-			exactly(4).of(database).startTransaction();
+			exactly(5).of(database).startTransaction();
 			will(returnValue(txn));
-			oneOf(database).containsContact(txn, contactId);
+			exactly(2).of(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			exactly(4).of(database).containsTransport(txn, transportId);
+			exactly(5).of(database).containsTransport(txn, transportId);
 			will(returnValue(false));
-			exactly(4).of(database).abortTransaction(txn);
+			exactly(5).of(database).abortTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, eventBus,
 				shutdown);
@@ -789,6 +799,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			db.endTransaction(transaction);
 		}
 
+		transaction = db.startTransaction(false);
+		try {
+			db.bindTransportKeys(transaction, contactId, transportId, keySetId);
+			fail();
+		} catch (NoSuchTransportException expected) {
+			// Expected
+		} finally {
+			db.endTransaction(transaction);
+		}
+
 		transaction = db.startTransaction(false);
 		try {
 			db.getTransportKeys(transaction, transportId);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
index a55a92bf78..ed7a357ed0 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
@@ -25,6 +25,7 @@ import java.util.Collection;
 import java.util.Random;
 
 import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -122,7 +123,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
 			will(returnValue(keySetId));
 		}});
 
-		assertEquals(singletonList(keySetId),
+		assertEquals(singletonMap(transportId, keySetId),
 				keyManager.addUnboundKeys(txn, secretKey, timestamp, alice));
 	}
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
index 4a92d4eb35..a57ce4e742 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
@@ -449,8 +449,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			}
 			// Save the keys that were rotated
 			oneOf(db).updateTransportKeys(txn1, asList(
-					new KeySet(keySetId, contactId, rotated),
-					new KeySet(keySetId1, null, rotated1)
+					new KeySet(keySetId1, null, rotated1),
+					new KeySet(keySetId, contactId, rotated)
 			));
 			// Schedule key rotation at the start of the next rotation period
 			oneOf(scheduler).schedule(with(any(Runnable.class)),
-- 
GitLab