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 a3f0f6d600844acc94fa813c4c81c8b39a5ca3af..065e5d2bc3edb66d7f2ae63cee0602dfbb304f30 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
@@ -57,6 +57,12 @@ public interface KeyManager {
 	void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys)
 		throws DbException;
 
+	/**
+	 * Returns true if we have keys that can be used for outgoing streams to
+	 * the given contact over the given transport.
+	 */
+	boolean canSendOutgoingStreams(ContactId c, TransportId t);
+
 	/**
 	 * 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/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index 860e0e4029f86c2c2e6d410510f46128dbfa7636..bbd5e5ec15c3e670db0217e8b5ccb63bc799182e 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
@@ -160,6 +160,12 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 		}
 	}
 
+	@Override
+	public boolean canSendOutgoingStreams(ContactId c, TransportId t) {
+		TransportKeyManager m = managers.get(t);
+		return m == null ? false : m.canSendOutgoingStreams(c);
+	}
+
 	@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 2af01fa4a6b00dcc6f23721d1c1be85204933396..5ca159a4259640317a80e24953948f27ce0c38e3 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
@@ -29,6 +29,8 @@ interface TransportKeyManager {
 
 	void removeContact(ContactId c);
 
+	boolean canSendOutgoingStreams(ContactId c);
+
 	@Nullable
 	StreamContext getStreamContext(Transaction txn, ContactId c)
 			throws DbException;
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 f6abc1c542e75d106479f5f8a7eae35b7ad3303f..d521531b0a5ef9641271d47c2968758d4d5005d3 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
@@ -278,6 +278,21 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		}
 	}
 
+	@Override
+	public boolean canSendOutgoingStreams(ContactId c) {
+		lock.lock();
+		try {
+			MutableKeySet ks = outContexts.get(c);
+			if (ks == null) return false;
+			MutableOutgoingKeys outKeys =
+					ks.getTransportKeys().getCurrentOutgoingKeys();
+			if (!outKeys.isActive()) throw new AssertionError();
+			return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED;
+		} finally {
+			lock.unlock();
+		}
+	}
+
 	@Override
 	public StreamContext getStreamContext(Transaction txn, ContactId c)
 			throws DbException {
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 df708df91a4ffb82f0452f5265a866f38018bb5c..84d6c34650050156b7203f4635ebb9079203062d 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
@@ -30,6 +30,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 
 import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
 import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
@@ -37,8 +38,10 @@ import static org.briarproject.bramble.api.transport.TransportConstants.REORDERI
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 
@@ -112,6 +115,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				db, transportCrypto, dbExecutor, scheduler, clock, transportId,
 				maxLatency);
 		transportKeyManager.start(txn);
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 	}
 
 	@Test
@@ -150,6 +154,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000 - 1;
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 	}
 
 	@Test
@@ -181,6 +186,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000 - 1;
 		assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
 				masterKey, timestamp, alice));
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 	}
 
 	@Test
@@ -192,6 +198,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				db, transportCrypto, dbExecutor, scheduler, clock, transportId,
 				maxLatency);
 		assertNull(transportKeyManager.getStreamContext(txn, contactId));
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 	}
 
 	@Test
@@ -212,6 +219,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 	}
 
@@ -238,6 +246,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
 		// The first request should return a stream context
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 		StreamContext ctx = transportKeyManager.getStreamContext(txn,
 				contactId);
 		assertNotNull(ctx);
@@ -247,6 +256,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		assertEquals(headerKey, ctx.getHeaderKey());
 		assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
 		// The second request should return null, the counter is exhausted
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 	}
 
@@ -266,6 +276,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 		// The tag should not be recognised
 		assertNull(transportKeyManager.getStreamContext(txn,
 				new byte[TAG_LENGTH]));
@@ -316,6 +327,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
 				alice);
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 		// Use the first tag (previous rotation period, stream number 0)
 		assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
 		byte[] tag = tags.get(0);
@@ -336,13 +348,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testKeysAreRotatedToCurrentPeriod() throws Exception {
 		TransportKeys transportKeys = createTransportKeys(1000, 0, true);
-		TransportKeys transportKeys1 = createTransportKeys(1000, 0, false);
-		Collection<KeySet> loaded = asList(
-				new KeySet(keySetId, contactId, transportKeys),
-				new KeySet(keySetId1, null, transportKeys1)
-		);
+		Collection<KeySet> loaded =
+				singletonList(new KeySet(keySetId, contactId, transportKeys));
 		TransportKeys rotated = createTransportKeys(1001, 0, true);
-		TransportKeys rotated1 = createTransportKeys(1001, 0, false);
 		Transaction txn = new Transaction(null, false);
 		Transaction txn1 = new Transaction(null, false);
 
@@ -356,8 +364,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			// Rotate the transport keys (the keys are unaffected)
 			oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
 			will(returnValue(transportKeys));
-			oneOf(transportCrypto).rotateTransportKeys(transportKeys1, 1000);
-			will(returnValue(transportKeys1));
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(transportCrypto).encodeTag(
@@ -381,9 +387,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			oneOf(transportCrypto).rotateTransportKeys(
 					with(any(TransportKeys.class)), with(1001L));
 			will(returnValue(rotated));
-			oneOf(transportCrypto).rotateTransportKeys(
-					with(any(TransportKeys.class)), with(1001L));
-			will(returnValue(rotated1));
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(transportCrypto).encodeTag(
@@ -392,10 +395,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated
-			oneOf(db).updateTransportKeys(txn1, asList(
-					new KeySet(keySetId1, null, rotated1),
-					new KeySet(keySetId, contactId, rotated)
-			));
+			oneOf(db).updateTransportKeys(txn1,
+					singletonList(new KeySet(keySetId, contactId, rotated)));
 			// Schedule key rotation at the start of the next rotation period
 			oneOf(scheduler).schedule(with(any(Runnable.class)),
 					with(rotationPeriodLength), with(MILLISECONDS));
@@ -408,6 +409,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				db, transportCrypto, dbExecutor, scheduler, clock, transportId,
 				maxLatency);
 		transportKeyManager.start(txn);
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 	}
 
 	@Test
@@ -451,11 +453,16 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
 				masterKey, timestamp, alice));
+		// The keys are unbound so no stream context should be returned
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
+		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 		transportKeyManager.bindKeys(txn, contactId, keySetId);
 		// The keys are inactive so no stream context should be returned
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 		assertNull(transportKeyManager.getStreamContext(txn, contactId));
 		transportKeyManager.activateKeys(txn, keySetId);
 		// The keys are active so a stream context should be returned
+		assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
 		StreamContext ctx = transportKeyManager.getStreamContext(txn,
 				contactId);
 		assertNotNull(ctx);
@@ -496,7 +503,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 		long timestamp = rotationPeriodLength * 1000;
 		assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
 				masterKey, timestamp, alice));
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 		transportKeyManager.removeKeys(txn, keySetId);
+		assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
 	}
 
 	private void expectAddContactNoRotation(boolean alice,