diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index e7cd834dffeb4531218320a64fa0099e36704e23..1aff5ba37258f851fd6228fac0b9c5d305e65cf6 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -69,8 +69,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 	private final SecureRandom secureRandom;
 	private final Clock clock;
 	private final DuplexPluginCallback callback;
-	private final int maxFrameLength;
-	private final long maxLatency, pollingInterval;
+	private final int maxFrameLength, maxLatency, pollingInterval;
 
 	private volatile boolean running = false;
 	private volatile boolean wasDisabled = false;
@@ -82,8 +81,8 @@ class DroidtoothPlugin implements DuplexPlugin {
 
 	DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
 			Context appContext, SecureRandom secureRandom, Clock clock,
-			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
-			long pollingInterval) {
+			DuplexPluginCallback callback, int maxFrameLength, int maxLatency,
+			int pollingInterval) {
 		this.ioExecutor = ioExecutor;
 		this.androidExecutor = androidExecutor;
 		this.appContext = appContext;
@@ -103,13 +102,13 @@ class DroidtoothPlugin implements DuplexPlugin {
 		return maxFrameLength;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 
-	public long getMaxIdleTime() {
+	public int getMaxIdleTime() {
 		// Bluetooth detects dead connections so we don't need keepalives
-		return Long.MAX_VALUE;
+		return Integer.MAX_VALUE;
 	}
 
 	public boolean start() throws IOException {
@@ -245,7 +244,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 		return true;
 	}
 
-	public long getPollingInterval() {
+	public int getPollingInterval() {
 		return pollingInterval;
 	}
 
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
index b7bb2028546aaa3dc508cffdbde4da0ab9bb3835..8cc21085d7a02686ae29b6da33d8348bdea0c274 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
@@ -16,8 +16,8 @@ import android.content.Context;
 public class DroidtoothPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
+	private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
 	private final Executor ioExecutor;
 	private final AndroidExecutor androidExecutor;
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java
index 68ff547a11fbf1ff573a13791fbbf57b880547c0..fa38875de088974578aaa8876aab450cb3464c87 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothTransportConnection.java
@@ -64,11 +64,11 @@ class DroidtoothTransportConnection implements DuplexTransportConnection {
 			return plugin.getMaxFrameLength();
 		}
 
-		public long getMaxLatency() {
+		public int getMaxLatency() {
 			return plugin.getMaxLatency();
 		}
 
-		public long getMaxIdleTime() {
+		public int getMaxIdleTime() {
 			return plugin.getMaxIdleTime();
 		}
 
diff --git a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java
index 25a9fbc540af0ecd69e09df90421da9d15db8548..6194e2ee8ff107e14f000f867ca3ce0444ab8d4c 100644
--- a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java
@@ -26,8 +26,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
 	private volatile BroadcastReceiver networkStateReceiver = null;
 
 	AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
-			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
-			long maxIdleTime, long pollingInterval) {
+			DuplexPluginCallback callback, int maxFrameLength, int maxLatency,
+			int maxIdleTime, int pollingInterval) {
 		super(ioExecutor, callback, maxFrameLength, maxLatency, maxIdleTime,
 				pollingInterval);
 		this.appContext = appContext;
diff --git a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
index 8160ac969b57f702db0db637476b1fe4e5a69aa2..7e0a5469c2410a868c6d084891f097754acbed14 100644
--- a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
@@ -12,9 +12,9 @@ import android.content.Context;
 public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long MAX_IDLE_TIME = 30 * 1000; // 30 seconds
-	private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
+	private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
+	private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
 	private final Executor ioExecutor;
 	private final Context appContext;
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index bd59adda5f316badcdff9f60788fe9338d2552fd..4222da1ba99683e8e6200249351ab8ca35afa6ef 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -75,8 +75,8 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 	private final Context appContext;
 	private final LocationUtils locationUtils;
 	private final DuplexPluginCallback callback;
-	private final int maxFrameLength, socketTimeout;
-	private final long maxLatency, maxIdleTime, pollingInterval;
+	private final int maxFrameLength, maxLatency, maxIdleTime, pollingInterval;
+	private final int socketTimeout;
 	private final File torDirectory, torFile, geoIpFile, configFile, doneFile;
 	private final File cookieFile, hostnameFile;
 	private final AtomicBoolean circuitBuilt;
@@ -90,8 +90,8 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 
 	TorPlugin(Executor ioExecutor, Context appContext,
 			LocationUtils locationUtils, DuplexPluginCallback callback,
-			int maxFrameLength, long maxLatency, long maxIdleTime,
-			long pollingInterval) {
+			int maxFrameLength, int maxLatency, int maxIdleTime,
+			int pollingInterval) {
 		this.ioExecutor = ioExecutor;
 		this.appContext = appContext;
 		this.locationUtils = locationUtils;
@@ -100,9 +100,9 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 		this.maxLatency = maxLatency;
 		this.maxIdleTime = maxIdleTime;
 		this.pollingInterval = pollingInterval;
-		if(2 * maxIdleTime > Integer.MAX_VALUE)
+		if(maxIdleTime > Integer.MAX_VALUE / 2)
 			socketTimeout = Integer.MAX_VALUE;
-		else socketTimeout = (int) (2 * maxIdleTime);
+		else socketTimeout = maxIdleTime * 2;
 		torDirectory = appContext.getDir("tor", MODE_PRIVATE);
 		torFile = new File(torDirectory, "tor");
 		geoIpFile = new File(torDirectory, "geoip");
@@ -121,11 +121,11 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 		return maxFrameLength;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 
-	public long getMaxIdleTime() {
+	public int getMaxIdleTime() {
 		return maxIdleTime;
 	}
 
@@ -504,7 +504,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 		return true;
 	}
 
-	public long getPollingInterval() {
+	public int getPollingInterval() {
 		return pollingInterval;
 	}
 
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
index 1260648c3a44f489f9b278e808d8149fb4d39ebf..bf44a35933f96c4bc84b792ffc2f1097b46fdc0f 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
@@ -18,9 +18,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
 			Logger.getLogger(TorPluginFactory.class.getName());
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long MAX_IDLE_TIME = 30 * 1000; // 30 seconds
-	private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
+	private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
+	private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
 	private final Executor ioExecutor;
 	private final Context appContext;
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java b/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java
index 081bba120511addc160626995df697443eedea7e..12880977f9dc27332ff78fd1a5ea8973e57e0465 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorTransportConnection.java
@@ -63,11 +63,11 @@ class TorTransportConnection implements DuplexTransportConnection {
 			return plugin.getMaxFrameLength();
 		}
 
-		public long getMaxLatency() {
+		public int getMaxLatency() {
 			return plugin.getMaxLatency();
 		}
 
-		public long getMaxIdleTime() {
+		public int getMaxIdleTime() {
 			return plugin.getMaxIdleTime();
 		}
 
diff --git a/briar-api/src/org/briarproject/api/crypto/KeyManager.java b/briar-api/src/org/briarproject/api/crypto/KeyManager.java
index 59e4d7af3083c5d034de0ef0040806a5162944a3..77c41132e01387a4182e6e2e99c3f96a7e30b60f 100644
--- a/briar-api/src/org/briarproject/api/crypto/KeyManager.java
+++ b/briar-api/src/org/briarproject/api/crypto/KeyManager.java
@@ -20,5 +20,5 @@ public interface KeyManager extends Service {
 	 * Called whenever an endpoint has been added. The initial secret is erased
 	 * before returning.
 	 */
-	void endpointAdded(Endpoint ep, long maxLatency, byte[] initialSecret);
+	void endpointAdded(Endpoint ep, int maxLatency, byte[] initialSecret);
 }
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index e7b55dd6d2bf857e89825fcdc12d7bdff18521e1..5159ac0220bc19863092d67a1fe6b0427c15143c 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -73,7 +73,7 @@ public interface DatabaseComponent {
 	 * Stores a transport and returns true if the transport was not previously
 	 * in the database.
 	 */
-	boolean addTransport(TransportId t, long maxLatency) throws DbException;
+	boolean addTransport(TransportId t, int maxLatency) throws DbException;
 
 	/**
 	 * Returns an acknowledgement for the given contact, or null if there are
@@ -88,14 +88,14 @@ public interface DatabaseComponent {
 	 * sendable messages that fit in the given length.
 	 */
 	Collection<byte[]> generateBatch(ContactId c, int maxLength,
-			long maxLatency) throws DbException;
+			int maxLatency) throws DbException;
 
 	/**
 	 * Returns an offer for the given contact for transmission over a
 	 * transport with the given maximum latency, or null if there are no
 	 * messages to offer.
 	 */
-	Offer generateOffer(ContactId c, int maxMessages, long maxLatency)
+	Offer generateOffer(ContactId c, int maxMessages, int maxLatency)
 			throws DbException;
 
 	/**
@@ -112,7 +112,7 @@ public interface DatabaseComponent {
 	 * sendable messages that fit in the given length.
 	 */
 	Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength,
-			long maxLatency) throws DbException;
+			int maxLatency) throws DbException;
 
 	/**
 	 * Returns a retention ack for the given contact, or null if no retention
@@ -125,7 +125,7 @@ public interface DatabaseComponent {
 	 * over a transport with the given latency. Returns null if no update is
 	 * due.
 	 */
-	RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency)
+	RetentionUpdate generateRetentionUpdate(ContactId c, int maxLatency)
 			throws DbException;
 
 	/**
@@ -139,7 +139,7 @@ public interface DatabaseComponent {
 	 * over a transport with the given latency. Returns null if no update is
 	 * due.
 	 */
-	SubscriptionUpdate generateSubscriptionUpdate(ContactId c, long maxLatency)
+	SubscriptionUpdate generateSubscriptionUpdate(ContactId c, int maxLatency)
 			throws DbException;
 
 	/**
@@ -155,7 +155,7 @@ public interface DatabaseComponent {
 	 * updates are due.
 	 */
 	Collection<TransportUpdate> generateTransportUpdates(ContactId c,
-			long maxLatency) throws DbException;
+			int maxLatency) throws DbException;
 
 	/**
 	 * Returns the status of all groups to which the user subscribes or can
@@ -227,8 +227,8 @@ public interface DatabaseComponent {
 	/** Returns all contacts who subscribe to the given group. */
 	Collection<Contact> getSubscribers(GroupId g) throws DbException;
 
-	/** Returns the maximum latencies of all local transports. */
-	Map<TransportId, Long> getTransportLatencies() throws DbException;
+	/** Returns the maximum latencies of all supported transports. */
+	Map<TransportId, Integer> getTransportLatencies() throws DbException;
 
 	/** Returns the number of unread messages in each subscribed group. */
 	Map<GroupId, Integer> getUnreadMessageCounts() throws DbException;
diff --git a/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java b/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java
index 7a1f599a6e59d452f782d61144678e5454c0a1ab..b775ce6c9e5dd88c112f6821fe157b4a0e68d853 100644
--- a/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java
+++ b/briar-api/src/org/briarproject/api/event/TransportAddedEvent.java
@@ -6,9 +6,9 @@ import org.briarproject.api.TransportId;
 public class TransportAddedEvent extends Event {
 
 	private final TransportId transportId;
-	private final long maxLatency;
+	private final int maxLatency;
 
-	public TransportAddedEvent(TransportId transportId, long maxLatency) {
+	public TransportAddedEvent(TransportId transportId, int maxLatency) {
 		this.transportId = transportId;
 		this.maxLatency = maxLatency;
 	}
@@ -17,7 +17,7 @@ public class TransportAddedEvent extends Event {
 		return transportId;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/messaging/MessagingSessionFactory.java b/briar-api/src/org/briarproject/api/messaging/MessagingSessionFactory.java
index d59b8ae9ab254c81ef184105a3f91cb9dd0503b6..8764f3e255bb0ed10e8a676b9887dfd63172b22d 100644
--- a/briar-api/src/org/briarproject/api/messaging/MessagingSessionFactory.java
+++ b/briar-api/src/org/briarproject/api/messaging/MessagingSessionFactory.java
@@ -12,8 +12,8 @@ public interface MessagingSessionFactory {
 			InputStream in);
 
 	MessagingSession createSimplexOutgoingSession(ContactId c, TransportId t,
-			long maxLatency, OutputStream out);
+			int maxLatency, OutputStream out);
 
 	MessagingSession createDuplexOutgoingSession(ContactId c, TransportId t,
-			long maxLatency, long maxIdleTime, OutputStream out);
+			int maxLatency, int maxIdleTime, OutputStream out);
 }
diff --git a/briar-api/src/org/briarproject/api/messaging/PacketWriter.java b/briar-api/src/org/briarproject/api/messaging/PacketWriter.java
index bc7b4e1de6d4086956003b46872a508e7a548800..1300e84c5b7730eb3f2088bf308e6dfcb561b0cd 100644
--- a/briar-api/src/org/briarproject/api/messaging/PacketWriter.java
+++ b/briar-api/src/org/briarproject/api/messaging/PacketWriter.java
@@ -29,4 +29,6 @@ public interface PacketWriter {
 	void writeTransportAck(TransportAck a) throws IOException;
 
 	void writeTransportUpdate(TransportUpdate u) throws IOException;
+
+	void flush() throws IOException;
 }
diff --git a/briar-api/src/org/briarproject/api/plugins/Plugin.java b/briar-api/src/org/briarproject/api/plugins/Plugin.java
index 1048f04cff3a5cc6feb73667a3e0558e684db6c6..eafb9100707c1db7d20cab3e747986d5a2fe2d28 100644
--- a/briar-api/src/org/briarproject/api/plugins/Plugin.java
+++ b/briar-api/src/org/briarproject/api/plugins/Plugin.java
@@ -15,10 +15,10 @@ public interface Plugin {
 	int getMaxFrameLength();
 
 	/** Returns the transport's maximum latency in milliseconds. */
-	long getMaxLatency();
+	int getMaxLatency();
 
 	/** Returns the transport's maximum idle time in milliseconds. */
-	long getMaxIdleTime();
+	int getMaxIdleTime();
 
 	/** Starts the plugin and returns true if it started successfully. */
 	boolean start() throws IOException;
@@ -39,7 +39,7 @@ public interface Plugin {
 	 * Returns the desired interval in milliseconds between calls to the
 	 * plugin's {@link #poll(Collection)} method.
 	 */
-	long getPollingInterval();
+	int getPollingInterval();
 
 	/**
 	 * Attempts to establish connections to contacts, passing any created
diff --git a/briar-api/src/org/briarproject/api/plugins/TransportConnectionWriter.java b/briar-api/src/org/briarproject/api/plugins/TransportConnectionWriter.java
index 4613f0e768e0ee80a0ae1b1f3d0d932a9ad49357..7f07411c635bae555543243ff13d4d0b78dd1c19 100644
--- a/briar-api/src/org/briarproject/api/plugins/TransportConnectionWriter.java
+++ b/briar-api/src/org/briarproject/api/plugins/TransportConnectionWriter.java
@@ -13,10 +13,10 @@ public interface TransportConnectionWriter {
 	int getMaxFrameLength();
 
 	/** Returns the maximum latency of the transport in milliseconds. */
-	long getMaxLatency();
+	int getMaxLatency();
 
 	/** Returns the maximum idle time of the transport in milliseconds. */
-	long getMaxIdleTime();
+	int getMaxIdleTime();
 
 	/** Returns the capacity of the transport connection in bytes. */
 	long getCapacity();
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index 5bce51798e1ce545ec8e51b5656a85006cf7239c..ba3c4ffafc2031fc19e8051a50db502013b7ebf3 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -152,7 +152,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: write.
 	 */
-	boolean addTransport(T txn, TransportId t, long maxLatency)
+	boolean addTransport(T txn, TransportId t, int maxLatency)
 			throws DbException;
 
 	/**
@@ -460,7 +460,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: write.
 	 */
-	RetentionUpdate getRetentionUpdate(T txn, ContactId c, long maxLatency)
+	RetentionUpdate getRetentionUpdate(T txn, ContactId c, int maxLatency)
 			throws DbException;
 
 	/**
@@ -499,7 +499,7 @@ interface Database<T> {
 	 * Locking: write.
 	 */
 	SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
-			long maxLatency) throws DbException;
+			int maxLatency) throws DbException;
 
 	/**
 	 * Returns a collection of transport acks for the given contact, or null if
@@ -511,11 +511,11 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Returns the maximum latencies of all local transports.
+	 * Returns the maximum latencies of all supported transports.
 	 * <p>
 	 * Locking: read.
 	 */
-	Map<TransportId, Long> getTransportLatencies(T txn) throws DbException;
+	Map<TransportId, Integer> getTransportLatencies(T txn) throws DbException;
 
 	/**
 	 * Returns a collection of transport updates for the given contact and
@@ -525,7 +525,7 @@ interface Database<T> {
 	 * Locking: write.
 	 */
 	Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c,
-			long maxLatency) throws DbException;
+			int maxLatency) throws DbException;
 
 	/**
 	 * Returns the number of unread messages in each subscribed group.
@@ -798,6 +798,6 @@ interface Database<T> {
 	 * <p>
 	 * Locking: write.
 	 */
-	void updateExpiryTime(T txn, ContactId c, MessageId m, long maxLatency)
+	void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency)
 			throws DbException;
 }
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index c22643f0be075806ecc43d52659faddb86a11bf5..1c8bc564d7cc6cbdedfef4a15bbcae1605d07d91 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -314,7 +314,7 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public boolean addTransport(TransportId t, long maxLatency)
+	public boolean addTransport(TransportId t, int maxLatency)
 			throws DbException {
 		boolean added;
 		lock.writeLock().lock();
@@ -357,7 +357,7 @@ DatabaseCleaner.Callback {
 	}
 
 	public Collection<byte[]> generateBatch(ContactId c, int maxLength,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
 		lock.writeLock().lock();
@@ -384,7 +384,7 @@ DatabaseCleaner.Callback {
 		return Collections.unmodifiableList(messages);
 	}
 
-	public Offer generateOffer(ContactId c, int maxMessages, long maxLatency)
+	public Offer generateOffer(ContactId c, int maxMessages, int maxLatency)
 			throws DbException {
 		Collection<MessageId> ids;
 		lock.writeLock().lock();
@@ -432,7 +432,7 @@ DatabaseCleaner.Callback {
 	}
 
 	public Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
 		lock.writeLock().lock();
@@ -478,7 +478,7 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency)
+	public RetentionUpdate generateRetentionUpdate(ContactId c, int maxLatency)
 			throws DbException {
 		lock.writeLock().lock();
 		try {
@@ -519,7 +519,7 @@ DatabaseCleaner.Callback {
 	}
 
 	public SubscriptionUpdate generateSubscriptionUpdate(ContactId c,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
@@ -560,7 +560,7 @@ DatabaseCleaner.Callback {
 	}
 
 	public Collection<TransportUpdate> generateTransportUpdates(ContactId c,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
@@ -932,12 +932,13 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Map<TransportId, Long> getTransportLatencies() throws DbException {
+	public Map<TransportId, Integer> getTransportLatencies()
+			throws DbException {
 		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				Map<TransportId, Long> latencies =
+				Map<TransportId, Integer> latencies =
 						db.getTransportLatencies(txn);
 				db.commitTransaction(txn);
 				return latencies;
diff --git a/briar-core/src/org/briarproject/db/ExponentialBackoff.java b/briar-core/src/org/briarproject/db/ExponentialBackoff.java
index 15444d11c15b52103e0222ca8d3fe20e4f0ea007..5248e8e50c888005bf0b275ad256462512b09199 100644
--- a/briar-core/src/org/briarproject/db/ExponentialBackoff.java
+++ b/briar-core/src/org/briarproject/db/ExponentialBackoff.java
@@ -11,13 +11,12 @@ class ExponentialBackoff {
 	 * transmissions increases exponentially. If the expiry time would
 	 * be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned.
 	 */
-	static long calculateExpiry(long now, long maxLatency, int txCount) {
+	static long calculateExpiry(long now, int maxLatency, int txCount) {
 		if(now < 0) throw new IllegalArgumentException();
 		if(maxLatency <= 0) throw new IllegalArgumentException();
 		if(txCount < 0) throw new IllegalArgumentException();
 		// The maximum round-trip time is twice the maximum latency
-		long roundTrip = maxLatency * 2;
-		if(roundTrip < 0) return Long.MAX_VALUE;
+		long roundTrip = maxLatency * 2L;
 		// The interval between transmissions is roundTrip * 2 ^ txCount
 		for(int i = 0; i < txCount; i++) {
 			roundTrip <<= 1;
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index fdeab4e543fb2d5806cf0244978e325bbbaccb03..d131b4a1d269b0beb9ea416d48691789c4ed5ee0 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -62,8 +62,8 @@ import org.briarproject.api.transport.TemporarySecret;
  */
 abstract class JdbcDatabase implements Database<Connection> {
 
-	private static final int SCHEMA_VERSION = 6;
-	private static final int MIN_SCHEMA_VERSION = 5;
+	private static final int SCHEMA_VERSION = 7;
+	private static final int MIN_SCHEMA_VERSION = 7;
 
 	private static final String CREATE_SETTINGS =
 			"CREATE TABLE settings"
@@ -213,7 +213,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String CREATE_TRANSPORTS =
 			"CREATE TABLE transports"
 					+ " (transportId VARCHAR NOT NULL,"
-					+ " maxLatency BIGINT NOT NULL,"
+					+ " maxLatency INT NOT NULL,"
 					+ " PRIMARY KEY (transportId))";
 
 	private static final String CREATE_TRANSPORT_CONFIGS =
@@ -866,7 +866,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean addTransport(Connection txn, TransportId t, long maxLatency)
+	public boolean addTransport(Connection txn, TransportId t, int maxLatency)
 			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2024,7 +2024,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public RetentionUpdate getRetentionUpdate(Connection txn, ContactId c,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2202,7 +2202,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public SubscriptionUpdate getSubscriptionUpdate(Connection txn, ContactId c,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2296,7 +2296,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Map<TransportId, Long> getTransportLatencies(Connection txn)
+	public Map<TransportId, Integer> getTransportLatencies(Connection txn)
 			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2304,10 +2304,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			String sql = "SELECT transportId, maxLatency FROM transports";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
-			Map<TransportId, Long> latencies = new HashMap<TransportId, Long>();
+			Map<TransportId, Integer> latencies =
+					new HashMap<TransportId, Integer>();
 			while(rs.next()){
 				TransportId id = new TransportId(rs.getString(1));
-				latencies.put(id, rs.getLong(2));
+				latencies.put(id, rs.getInt(2));
 			}
 			rs.close();
 			ps.close();
@@ -2320,7 +2321,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public Collection<TransportUpdate> getTransportUpdates(Connection txn,
-			ContactId c, long maxLatency) throws DbException {
+			ContactId c, int maxLatency) throws DbException {
 		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -3301,7 +3302,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
-			long maxLatency) throws DbException {
+			int maxLatency) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java
index 9c0144620286c09c6bc48f28de6faf93b322434f..1fa00401217ad5717afd9f7f086adc47bb916d6f 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -285,7 +285,7 @@ abstract class Connector extends Thread {
 		db.setRemoteProperties(contactId, remoteProps);
 		// Create an endpoint for each transport shared with the contact
 		List<TransportId> ids = new ArrayList<TransportId>();
-		Map<TransportId, Long> latencies = db.getTransportLatencies();
+		Map<TransportId, Integer> latencies = db.getTransportLatencies();
 		for(TransportId id : localProps.keySet()) {
 			if(latencies.containsKey(id) && remoteProps.containsKey(id))
 				ids.add(id);
@@ -296,7 +296,7 @@ abstract class Connector extends Thread {
 		for(int i = 0; i < size; i++) {
 			TransportId id = ids.get(i);
 			Endpoint ep = new Endpoint(contactId, id, epoch, alice);
-			long maxLatency = latencies.get(id);
+			int maxLatency = latencies.get(id);
 			try {
 				db.addEndpoint(ep);
 			} catch(NoSuchTransportException e) {
diff --git a/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java b/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java
index 13e8395545bf65fa0da2b9d176067f6aaff36567..23177097f912900e73d18b20c320528d6aa5bda6 100644
--- a/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/messaging/DuplexOutgoingSession.java
@@ -6,7 +6,6 @@ import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.Collection;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
@@ -37,7 +36,6 @@ import org.briarproject.api.messaging.Ack;
 import org.briarproject.api.messaging.MessagingSession;
 import org.briarproject.api.messaging.Offer;
 import org.briarproject.api.messaging.PacketWriter;
-import org.briarproject.api.messaging.PacketWriterFactory;
 import org.briarproject.api.messaging.Request;
 import org.briarproject.api.messaging.RetentionAck;
 import org.briarproject.api.messaging.RetentionUpdate;
@@ -45,16 +43,18 @@ import org.briarproject.api.messaging.SubscriptionAck;
 import org.briarproject.api.messaging.SubscriptionUpdate;
 import org.briarproject.api.messaging.TransportAck;
 import org.briarproject.api.messaging.TransportUpdate;
+import org.briarproject.api.system.Clock;
 
 /**
  * An outgoing {@link org.briarproject.api.messaging.MessagingSession
  * MessagingSession} suitable for duplex transports. The session offers
  * messages before sending them, keeps its output stream open when there are no
- * more packets to send, and reacts to events that make packets available to
- * send.
+ * packets to send, and reacts to events that make packets available to send.
  */
 class DuplexOutgoingSession implements MessagingSession, EventListener {
 
+	// Check for retransmittable packets once every 60 seconds
+	private static final int RETX_QUERY_INTERVAL = 60 * 1000;
 	private static final Logger LOG =
 			Logger.getLogger(DuplexOutgoingSession.class.getName());
 
@@ -66,28 +66,32 @@ class DuplexOutgoingSession implements MessagingSession, EventListener {
 	private final DatabaseComponent db;
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
+	private final Clock clock;
 	private final ContactId contactId;
 	private final TransportId transportId;
-	private final long maxLatency, maxIdleTime;
-	private final OutputStream out;
+	private final int maxLatency, maxIdleTime;
 	private final PacketWriter packetWriter;
 	private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
 
+	// The following must only be accessed on the writer thread
+	private long nextKeepalive = 0, nextRetxQuery = 0;
+	private boolean dataToFlush = true;
+
 	private volatile boolean interrupted = false;
 
 	DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
-			EventBus eventBus, PacketWriterFactory packetWriterFactory,
-			ContactId contactId, TransportId transportId, long maxLatency,
-			long maxIdleTime, OutputStream out) {
+			EventBus eventBus, Clock clock, ContactId contactId,
+			TransportId transportId, int maxLatency, int maxIdleTime,
+			PacketWriter packetWriter) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
+		this.clock = clock;
 		this.contactId = contactId;
 		this.transportId = transportId;
 		this.maxLatency = maxLatency;
 		this.maxIdleTime = maxIdleTime;
-		this.out = out;
-		packetWriter = packetWriterFactory.createPacketWriter(out);
+		this.packetWriter = packetWriter;
 		writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
 	}
 
@@ -105,21 +109,50 @@ class DuplexOutgoingSession implements MessagingSession, EventListener {
 			dbExecutor.execute(new GenerateBatch());
 			dbExecutor.execute(new GenerateOffer());
 			dbExecutor.execute(new GenerateRequest());
+			long now = clock.currentTimeMillis();
+			nextKeepalive = now + maxIdleTime;
+			nextRetxQuery = now + RETX_QUERY_INTERVAL;
 			// Write packets until interrupted
 			try {
 				while(!interrupted) {
-					// Flush the stream if it's going to be idle
-					if(writerTasks.isEmpty()) out.flush();
-					ThrowingRunnable<IOException> task =
-							writerTasks.poll(maxIdleTime, MILLISECONDS);
+					// Work out how long we should wait for a packet
+					now = clock.currentTimeMillis();
+					long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
+					if(wait < 0) wait = 0;
+					// Flush any unflushed data if we're going to wait
+					if(wait > 0 && dataToFlush && writerTasks.isEmpty()) {
+						packetWriter.flush();
+						dataToFlush = false;
+						nextKeepalive = now + maxIdleTime;
+					}
+					// Wait for a packet
+					ThrowingRunnable<IOException> task = writerTasks.poll(wait,
+							MILLISECONDS);
 					if(task == null) {
-						LOG.info("Idle timeout");
-						continue; // Flush and wait again
+						now = clock.currentTimeMillis();
+						if(now >= nextRetxQuery) {
+							// Check for retransmittable packets
+							dbExecutor.execute(new GenerateTransportUpdates());
+							dbExecutor.execute(new GenerateSubscriptionUpdate());
+							dbExecutor.execute(new GenerateRetentionUpdate());
+							dbExecutor.execute(new GenerateBatch());
+							dbExecutor.execute(new GenerateOffer());
+							nextRetxQuery = now + RETX_QUERY_INTERVAL;
+						}
+						if(now >= nextKeepalive) {
+							// Flush the stream to keep it alive
+							packetWriter.flush();
+							dataToFlush = false;
+							nextKeepalive = now + maxIdleTime;
+						}
+					} else if(task == CLOSE) {
+						break;
+					} else {
+						task.run();
+						dataToFlush = true;
 					}
-					if(task == CLOSE) break;
-					task.run();
 				}
-				out.flush();
+				if(dataToFlush) packetWriter.flush();
 			} catch(InterruptedException e) {
 				LOG.info("Interrupted while waiting for a packet to write");
 				Thread.currentThread().interrupt();
diff --git a/briar-core/src/org/briarproject/messaging/IncomingSession.java b/briar-core/src/org/briarproject/messaging/IncomingSession.java
index b85ccda0db618ae8aef73072f6d1602913f8f91a..1bd8acd66cbdeee28ade9ef0537cbf1a715e174e 100644
--- a/briar-core/src/org/briarproject/messaging/IncomingSession.java
+++ b/briar-core/src/org/briarproject/messaging/IncomingSession.java
@@ -3,7 +3,6 @@ package org.briarproject.messaging;
 import static java.util.logging.Level.WARNING;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.security.GeneralSecurityException;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
@@ -25,7 +24,6 @@ import org.briarproject.api.messaging.MessageVerifier;
 import org.briarproject.api.messaging.MessagingSession;
 import org.briarproject.api.messaging.Offer;
 import org.briarproject.api.messaging.PacketReader;
-import org.briarproject.api.messaging.PacketReaderFactory;
 import org.briarproject.api.messaging.Request;
 import org.briarproject.api.messaging.RetentionAck;
 import org.briarproject.api.messaging.RetentionUpdate;
@@ -56,9 +54,8 @@ class IncomingSession implements MessagingSession, EventListener {
 
 	IncomingSession(DatabaseComponent db, Executor dbExecutor,
 			Executor cryptoExecutor, EventBus eventBus,
-			MessageVerifier messageVerifier,
-			PacketReaderFactory packetReaderFactory, ContactId contactId,
-			TransportId transportId, InputStream in) {
+			MessageVerifier messageVerifier, ContactId contactId,
+			TransportId transportId, PacketReader packetReader) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.cryptoExecutor = cryptoExecutor;
@@ -66,7 +63,7 @@ class IncomingSession implements MessagingSession, EventListener {
 		this.messageVerifier = messageVerifier;
 		this.contactId = contactId;
 		this.transportId = transportId;
-		packetReader = packetReaderFactory.createPacketReader(in);
+		this.packetReader = packetReader;
 	}
 
 	public void run() throws IOException {
diff --git a/briar-core/src/org/briarproject/messaging/MessagingSessionFactoryImpl.java b/briar-core/src/org/briarproject/messaging/MessagingSessionFactoryImpl.java
index 9ab638aa0348909bd396690f246227450957bb9a..c7a31f6094642f983c2549ff460b989c3579f6c5 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingSessionFactoryImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingSessionFactoryImpl.java
@@ -15,8 +15,11 @@ import org.briarproject.api.event.EventBus;
 import org.briarproject.api.messaging.MessageVerifier;
 import org.briarproject.api.messaging.MessagingSession;
 import org.briarproject.api.messaging.MessagingSessionFactory;
+import org.briarproject.api.messaging.PacketReader;
 import org.briarproject.api.messaging.PacketReaderFactory;
+import org.briarproject.api.messaging.PacketWriter;
 import org.briarproject.api.messaging.PacketWriterFactory;
+import org.briarproject.api.system.Clock;
 
 class MessagingSessionFactoryImpl implements MessagingSessionFactory {
 
@@ -24,6 +27,7 @@ class MessagingSessionFactoryImpl implements MessagingSessionFactory {
 	private final Executor dbExecutor, cryptoExecutor;
 	private final MessageVerifier messageVerifier;
 	private final EventBus eventBus;
+	private final Clock clock;
 	private final PacketReaderFactory packetReaderFactory;
 	private final PacketWriterFactory packetWriterFactory;
 
@@ -31,7 +35,7 @@ class MessagingSessionFactoryImpl implements MessagingSessionFactory {
 	MessagingSessionFactoryImpl(DatabaseComponent db,
 			@DatabaseExecutor Executor dbExecutor,
 			@CryptoExecutor Executor cryptoExecutor,
-			MessageVerifier messageVerifier, EventBus eventBus,
+			MessageVerifier messageVerifier, EventBus eventBus, Clock clock,
 			PacketReaderFactory packetReaderFactory,
 			PacketWriterFactory packetWriterFactory) {
 		this.db = db;
@@ -39,26 +43,29 @@ class MessagingSessionFactoryImpl implements MessagingSessionFactory {
 		this.cryptoExecutor = cryptoExecutor;
 		this.messageVerifier = messageVerifier;
 		this.eventBus = eventBus;
+		this.clock = clock;
 		this.packetReaderFactory = packetReaderFactory;
 		this.packetWriterFactory = packetWriterFactory;
 	}
 
 	public MessagingSession createIncomingSession(ContactId c, TransportId t,
 			InputStream in) {
+		PacketReader packetReader = packetReaderFactory.createPacketReader(in);
 		return new IncomingSession(db, dbExecutor, cryptoExecutor, eventBus,
-				messageVerifier, packetReaderFactory, c, t, in);
+				messageVerifier, c, t, packetReader);
 	}
 
 	public MessagingSession createSimplexOutgoingSession(ContactId c,
-			TransportId t, long maxLatency, OutputStream out) {
-		return new SimplexOutgoingSession(db, dbExecutor, eventBus,
-				packetWriterFactory, c, t, maxLatency, out);
+			TransportId t, int maxLatency, OutputStream out) {
+		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
+		return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
+				maxLatency, packetWriter);
 	}
 
 	public MessagingSession createDuplexOutgoingSession(ContactId c,
-			TransportId t, long maxLatency, long maxIdleTime,
-			OutputStream out) {
-		return new DuplexOutgoingSession(db, dbExecutor, eventBus,
-				packetWriterFactory, c, t, maxLatency, maxIdleTime, out);
+			TransportId t, int maxLatency, int maxIdleTime, OutputStream out) {
+		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
+		return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, t,
+				maxLatency, maxIdleTime, packetWriter);
 	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java b/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java
index 4b0efd4a7ea5fcec06f1be25205502458717b7bf..1ef8e348c92c4efc555f94c909c757fb674d079f 100644
--- a/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java
+++ b/briar-core/src/org/briarproject/messaging/PacketWriterImpl.java
@@ -143,4 +143,8 @@ class PacketWriterImpl implements PacketWriter {
 		w.writeInteger(u.getVersion());
 		w.writeStructEnd();
 	}
+
+	public void flush() throws IOException {
+		out.flush();
+	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java b/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java
index 51ec4d81a49d124a5d2db805b9c47f12c1070870..6d8dfabf1ae815bdebaba6fef642a40717c7368f 100644
--- a/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java
+++ b/briar-core/src/org/briarproject/messaging/SimplexOutgoingSession.java
@@ -5,7 +5,6 @@ import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.Collection;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
@@ -26,7 +25,6 @@ import org.briarproject.api.event.TransportRemovedEvent;
 import org.briarproject.api.messaging.Ack;
 import org.briarproject.api.messaging.MessagingSession;
 import org.briarproject.api.messaging.PacketWriter;
-import org.briarproject.api.messaging.PacketWriterFactory;
 import org.briarproject.api.messaging.RetentionAck;
 import org.briarproject.api.messaging.RetentionUpdate;
 import org.briarproject.api.messaging.SubscriptionAck;
@@ -55,8 +53,7 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
 	private final EventBus eventBus;
 	private final ContactId contactId;
 	private final TransportId transportId;
-	private final long maxLatency;
-	private final OutputStream out;
+	private final int maxLatency;
 	private final PacketWriter packetWriter;
 	private final AtomicInteger outstandingQueries;
 	private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -64,17 +61,15 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
 	private volatile boolean interrupted = false;
 
 	SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
-			EventBus eventBus, PacketWriterFactory packetWriterFactory,
-			ContactId contactId, TransportId transportId, long maxLatency,
-			OutputStream out) {
+			EventBus eventBus, ContactId contactId, TransportId transportId,
+			int maxLatency, PacketWriter packetWriter) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.eventBus = eventBus;
 		this.contactId = contactId;
 		this.transportId = transportId;
 		this.maxLatency = maxLatency;
-		this.out = out;
-		packetWriter = packetWriterFactory.createPacketWriter(out);
+		this.packetWriter = packetWriter;
 		outstandingQueries = new AtomicInteger(8); // One per type of packet
 		writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
 	}
@@ -98,7 +93,7 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
 					if(task == CLOSE) break;
 					task.run();
 				}
-				out.flush();
+				packetWriter.flush();
 			} catch(InterruptedException e) {
 				LOG.info("Interrupted while waiting for a packet to write");
 				Thread.currentThread().interrupt();
diff --git a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
index 9c298b02321bf07c3eabb2ca361c1ed36a0bf1c5..a187760aafdfcce5dc7700c66c93ccd266af33ee 100644
--- a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
+++ b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
@@ -28,8 +28,7 @@ public abstract class FilePlugin implements SimplexPlugin {
 	protected final Executor ioExecutor;
 	protected final FileUtils fileUtils;
 	protected final SimplexPluginCallback callback;
-	protected final int maxFrameLength;
-	protected final long maxLatency;
+	protected final int maxFrameLength, maxLatency;
 
 	protected volatile boolean running = false;
 
@@ -40,7 +39,7 @@ public abstract class FilePlugin implements SimplexPlugin {
 
 	protected FilePlugin(Executor ioExecutor, FileUtils fileUtils,
 			SimplexPluginCallback callback, int maxFrameLength,
-			long maxLatency) {
+			int maxLatency) {
 		this.ioExecutor = ioExecutor;
 		this.fileUtils = fileUtils;
 		this.callback = callback;
@@ -52,12 +51,12 @@ public abstract class FilePlugin implements SimplexPlugin {
 		return maxFrameLength;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 
-	public long getMaxIdleTime() {
-		return Long.MAX_VALUE; // We don't need keepalives
+	public int getMaxIdleTime() {
+		return Integer.MAX_VALUE; // We don't need keepalives
 	}
 
 	public boolean isRunning() {
diff --git a/briar-core/src/org/briarproject/plugins/file/FileTransportWriter.java b/briar-core/src/org/briarproject/plugins/file/FileTransportWriter.java
index ecba989ffec1f52c9e9a8bf0dfbf308e8bb9865f..c2ffcde0892b49a924165531a767f69901a11316 100644
--- a/briar-core/src/org/briarproject/plugins/file/FileTransportWriter.java
+++ b/briar-core/src/org/briarproject/plugins/file/FileTransportWriter.java
@@ -31,11 +31,11 @@ class FileTransportWriter implements TransportConnectionWriter {
 		return plugin.getMaxFrameLength();
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return plugin.getMaxLatency();
 	}
 
-	public long getMaxIdleTime() {
+	public int getMaxIdleTime() {
 		return plugin.getMaxIdleTime();
 	}
 
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
index d585fa83e15e0aea2467494aefe3a383690b029c..6f15c6fe81d5f0e1b51e8981e177c4e6967a036b 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
@@ -17,8 +17,8 @@ class LanTcpPlugin extends TcpPlugin {
 	static final TransportId ID = new TransportId("lan");
 
 	LanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
-			int maxFrameLength, long maxLatency, long maxIdleTime,
-			long pollingInterval) {
+			int maxFrameLength, int maxLatency, int maxIdleTime,
+			int pollingInterval) {
 		super(ioExecutor, callback, maxFrameLength, maxLatency, maxIdleTime,
 				pollingInterval);
 	}
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
index 0d7aef843f3e8d2915d1b9fd21c529c557ecfb5e..8527df94137b60548894abc24be5541d62c6846e 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
@@ -10,9 +10,9 @@ import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 public class LanTcpPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long MAX_IDLE_TIME = 30 * 1000; // 30 seconds
-	private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
+	private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
+	private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
 	private final Executor ioExecutor;
 
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index d8dd77be61c9030d5a5ef6d37fe2b0b178f240c1..0a4946b4824b777b449a6ca2c4805f6c68946caa 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -37,8 +37,8 @@ abstract class TcpPlugin implements DuplexPlugin {
 
 	protected final Executor ioExecutor;
 	protected final DuplexPluginCallback callback;
-	protected final int maxFrameLength, socketTimeout;
-	protected final long maxLatency, maxIdleTime, pollingInterval;
+	protected final int maxFrameLength, maxLatency, maxIdleTime;
+	protected final int pollingInterval, socketTimeout;
 
 	protected volatile boolean running = false;
 	protected volatile ServerSocket socket = null;
@@ -53,28 +53,28 @@ abstract class TcpPlugin implements DuplexPlugin {
 	protected abstract boolean isConnectable(InetSocketAddress remote);
 
 	protected TcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
-			int maxFrameLength, long maxLatency, long maxIdleTime,
-			long pollingInterval) {
+			int maxFrameLength, int maxLatency, int maxIdleTime,
+			int pollingInterval) {
 		this.ioExecutor = ioExecutor;
 		this.callback = callback;
 		this.maxFrameLength = maxFrameLength;
 		this.maxLatency = maxLatency;
 		this.maxIdleTime = maxIdleTime;
 		this.pollingInterval = pollingInterval;
-		if(2 * maxIdleTime > Integer.MAX_VALUE)
+		if(maxIdleTime > Integer.MAX_VALUE / 2)
 			socketTimeout = Integer.MAX_VALUE;
-		else socketTimeout = (int) (2 * maxIdleTime);
+		else socketTimeout = maxIdleTime * 2;
 	}
 
 	public int getMaxFrameLength() {
 		return maxFrameLength;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 
-	public long getMaxIdleTime() {
+	public int getMaxIdleTime() {
 		return maxIdleTime;
 	}
 
@@ -171,7 +171,7 @@ abstract class TcpPlugin implements DuplexPlugin {
 		return true;
 	}
 
-	public long getPollingInterval() {
+	public int getPollingInterval() {
 		return pollingInterval;
 	}
 
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java b/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java
index 2df916265d20a5f151025becc2285948bc2c9f5c..fff4ee01b1d8f78758c78b4410a2a931a5896f16 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpTransportConnection.java
@@ -63,11 +63,11 @@ class TcpTransportConnection implements DuplexTransportConnection {
 			return plugin.getMaxFrameLength();
 		}
 
-		public long getMaxLatency() {
+		public int getMaxLatency() {
 			return plugin.getMaxLatency();
 		}
 
-		public long getMaxIdleTime() {
+		public int getMaxIdleTime() {
 			return plugin.getMaxIdleTime();
 		}
 
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
index 8bc290c9380a928d5c38b622c813d00ab5c42885..67be0c72166d1293420b9e597957451a63b980e7 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
@@ -20,9 +20,9 @@ class WanTcpPlugin extends TcpPlugin {
 
 	private volatile MappingResult mappingResult;
 
-	WanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
-			int maxFrameLength, long maxLatency, long maxIdleTime,
-			long pollingInterval, PortMapper portMapper) {
+	WanTcpPlugin(Executor ioExecutor, PortMapper portMapper,
+			DuplexPluginCallback callback, int maxFrameLength, int maxLatency,
+			int maxIdleTime, int pollingInterval) {
 		super(ioExecutor, callback, maxFrameLength, maxLatency, maxIdleTime,
 				pollingInterval);
 		this.portMapper = portMapper;
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
index c7381b12559451a199b9fd432ee6987195e0c7a5..8129661ffc65c5c2c8c431653339eea0f13559d1 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
@@ -11,9 +11,9 @@ import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 public class WanTcpPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long MAX_IDLE_TIME = 30 * 1000; // 30 seconds
-	private static final long POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
+	private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
+	private static final int POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes
 
 	private final Executor ioExecutor;
 	private final ShutdownManager shutdownManager;
@@ -29,8 +29,8 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new WanTcpPlugin(ioExecutor, callback, MAX_FRAME_LENGTH,
-				MAX_LATENCY, MAX_IDLE_TIME, POLLING_INTERVAL,
-				new PortMapperImpl(shutdownManager));
+		return new WanTcpPlugin(ioExecutor, new PortMapperImpl(shutdownManager),
+				callback, MAX_FRAME_LENGTH, MAX_LATENCY, MAX_IDLE_TIME,
+				POLLING_INTERVAL);
 	}
 }
diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
index 16db0b2167f094e89cf8ecffe3c33a9b81fd36b0..735e9f17bab18f14e525cbc0299463552a586350 100644
--- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
+++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
@@ -51,7 +51,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 	private final Timer timer;
 
 	// All of the following are locking: this
-	private final Map<TransportId, Long> maxLatencies;
+	private final Map<TransportId, Integer> maxLatencies;
 	private final Map<EndpointKey, TemporarySecret> oldSecrets;
 	private final Map<EndpointKey, TemporarySecret> currentSecrets;
 	private final Map<EndpointKey, TemporarySecret> newSecrets;
@@ -66,7 +66,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 		this.tagRecogniser = tagRecogniser;
 		this.clock = clock;
 		this.timer = timer;
-		maxLatencies = new HashMap<TransportId, Long>();
+		maxLatencies = new HashMap<TransportId, Integer>();
 		oldSecrets = new HashMap<EndpointKey, TemporarySecret>();
 		currentSecrets = new HashMap<EndpointKey, TemporarySecret>();
 		newSecrets = new HashMap<EndpointKey, TemporarySecret>();
@@ -116,7 +116,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 		Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
 		for(TemporarySecret s : secrets) {
 			// Discard the secret if the transport has been removed
-			Long maxLatency = maxLatencies.get(s.getTransportId());
+			Integer maxLatency = maxLatencies.get(s.getTransportId());
 			if(maxLatency == null) {
 				LOG.info("Discarding obsolete secret");
 				ByteUtils.erase(s.getSecret());
@@ -168,7 +168,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 		Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
 		for(Entry<EndpointKey, TemporarySecret> e : newest.entrySet()) {
 			TemporarySecret s = e.getValue();
-			Long maxLatency = maxLatencies.get(s.getTransportId());
+			Integer maxLatency = maxLatencies.get(s.getTransportId());
 			if(maxLatency == null) throw new IllegalStateException();
 			// Work out which rotation period we're in
 			long elapsed = now - s.getEpoch();
@@ -255,7 +255,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
 		return new StreamContext(c, t, secret, streamNumber, s.getAlice());
 	}
 
-	public synchronized void endpointAdded(Endpoint ep, long maxLatency,
+	public synchronized void endpointAdded(Endpoint ep, int maxLatency,
 			byte[] initialSecret) {
 		maxLatencies.put(ep.getTransportId(), maxLatency);
 		// Work out which rotation period we're in
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
index 4a90f76ba1da6e02d4a229a69ce8e3f761265873..be87193edb12fc4cd4a94116cc1e76ee22b84732 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
@@ -46,8 +46,7 @@ class BluetoothPlugin implements DuplexPlugin {
 	private final Clock clock;
 	private final SecureRandom secureRandom;
 	private final DuplexPluginCallback callback;
-	private final int maxFrameLength;
-	private final long maxLatency, pollingInterval;
+	private final int maxFrameLength, maxLatency, pollingInterval;
 	private final Semaphore discoverySemaphore = new Semaphore(1);
 
 	private volatile boolean running = false;
@@ -55,8 +54,8 @@ class BluetoothPlugin implements DuplexPlugin {
 	private volatile LocalDevice localDevice = null;
 
 	BluetoothPlugin(Executor ioExecutor, Clock clock, SecureRandom secureRandom,
-			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
-			long pollingInterval) {
+			DuplexPluginCallback callback, int maxFrameLength, int maxLatency,
+			int pollingInterval) {
 		this.ioExecutor = ioExecutor;
 		this.clock = clock;
 		this.secureRandom = secureRandom;
@@ -74,13 +73,13 @@ class BluetoothPlugin implements DuplexPlugin {
 		return maxFrameLength;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 
-	public long getMaxIdleTime() {
+	public int getMaxIdleTime() {
 		// Bluetooth detects dead connections so we don't need keepalives
-		return Long.MAX_VALUE;
+		return Integer.MAX_VALUE;
 	}
 
 	public boolean start() throws IOException {
@@ -186,7 +185,7 @@ class BluetoothPlugin implements DuplexPlugin {
 		return true;
 	}
 
-	public long getPollingInterval() {
+	public int getPollingInterval() {
 		return pollingInterval;
 	}
 
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
index 446846305b840605d64c9504fc2a2a3077689920..a25a73c3706b941595ac14818a2e10ccfe54a6fa 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
@@ -13,8 +13,8 @@ import org.briarproject.system.SystemClock;
 public class BluetoothPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
+	private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
 	private final Executor ioExecutor;
 	private final SecureRandom secureRandom;
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java
index 3de6a6a105c9f4077a0d179ce0fd98987a80cba6..0767c42d9a045373e8f8138dd53eb0e81a8e7c50 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothTransportConnection.java
@@ -64,11 +64,11 @@ class BluetoothTransportConnection implements DuplexTransportConnection {
 			return plugin.getMaxFrameLength();
 		}
 
-		public long getMaxLatency() {
+		public int getMaxLatency() {
 			return plugin.getMaxLatency();
 		}
 
-		public long getMaxIdleTime() {
+		public int getMaxIdleTime() {
 			return plugin.getMaxIdleTime();
 		}
 
diff --git a/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java b/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java
index 2d8fd287616093ff0ddd54b5c5f9adbdd75ad412..a2564906534d73d207a82bd5d27608fc751bcb81 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java
@@ -13,14 +13,14 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 
 	private final Executor ioExecutor;
 	private final RemovableDriveFinder finder;
-	private final long pollingInterval;
+	private final int pollingInterval;
 	private final Object pollingLock = new Object();
 
 	private volatile boolean running = false;
 	private volatile Callback callback = null;
 
 	public PollingRemovableDriveMonitor(Executor ioExecutor,
-			RemovableDriveFinder finder, long pollingInterval) {
+			RemovableDriveFinder finder, int pollingInterval) {
 		this.ioExecutor = ioExecutor;
 		this.finder = finder;
 		this.pollingInterval = pollingInterval;
diff --git a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java
index bdf9d84ef9f9062ac02186fddd09d15e547c5a6a..faaf4b01c4424a25470c5cc65a27b621d7519d6e 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java
@@ -29,7 +29,7 @@ implements RemovableDriveMonitor.Callback {
 
 	RemovableDrivePlugin(Executor ioExecutor, FileUtils fileUtils,
 			SimplexPluginCallback callback, RemovableDriveFinder finder,
-			RemovableDriveMonitor monitor, int maxFrameLength, long maxLatency) {
+			RemovableDriveMonitor monitor, int maxFrameLength, int maxLatency) {
 		super(ioExecutor, fileUtils, callback, maxFrameLength, maxLatency);
 		this.finder = finder;
 		this.monitor = monitor;
@@ -54,7 +54,7 @@ implements RemovableDriveMonitor.Callback {
 		return false;
 	}
 
-	public long getPollingInterval() {
+	public int getPollingInterval() {
 		throw new UnsupportedOperationException();
 	}
 
diff --git a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
index 01688d40344aee7171076077cdd4ca3b09ecfda5..4b7d6835fc95a019251d1aed1bb1a33b5416b1d6 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
@@ -14,8 +14,8 @@ import org.briarproject.util.OsUtils;
 public class RemovableDrivePluginFactory implements SimplexPluginFactory {
 
 	// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
-	private static final long MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
-	private static final long POLLING_INTERVAL = 10 * 1000; // 10 seconds
+	private static final int MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
+	private static final int POLLING_INTERVAL = 10 * 1000; // 10 seconds
 
 	private final Executor ioExecutor;
 	private final FileUtils fileUtils;
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index 98362849775b05c8405b7449322a1dc704ff2904..092eeec8baa45406ecb42548dd50b1e00e1b206e 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -32,21 +32,18 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	private final ModemFactory modemFactory;
 	private final SerialPortList serialPortList;
 	private final DuplexPluginCallback callback;
-	private final int maxFrameLength;
-	private final long maxLatency, pollingInterval;
+	private final int maxFrameLength, maxLatency;
 
 	private volatile boolean running = false;
 	private volatile Modem modem = null;
 
 	ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList,
-			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
-			long pollingInterval) {
+			DuplexPluginCallback callback, int maxFrameLength, int maxLatency) {
 		this.modemFactory = modemFactory;
 		this.serialPortList = serialPortList;
 		this.callback = callback;
 		this.maxFrameLength = maxFrameLength;
 		this.maxLatency = maxLatency;
-		this.pollingInterval = pollingInterval;
 	}
 
 	public TransportId getId() {
@@ -57,13 +54,13 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 		return maxFrameLength;
 	}
 
-	public long getMaxLatency() {
+	public int getMaxLatency() {
 		return maxLatency;
 	}
 
-	public long getMaxIdleTime() {
+	public int getMaxIdleTime() {
 		// FIXME: Do we need keepalives for this transport?
-		return Long.MAX_VALUE;
+		return Integer.MAX_VALUE;
 	}
 
 	public boolean start() {
@@ -103,8 +100,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 		return false;
 	}
 
-	public long getPollingInterval() {
-		return pollingInterval;
+	public int getPollingInterval() {
+		throw new UnsupportedOperationException();
 	}
 
 	public void poll(Collection<ContactId> connected) {
@@ -226,11 +223,11 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 				return getMaxFrameLength();
 			}
 
-			public long getMaxLatency() {
+			public int getMaxLatency() {
 				return getMaxLatency();
 			}
 
-			public long getMaxIdleTime() {
+			public int getMaxIdleTime() {
 				return getMaxIdleTime();
 			}
 
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
index 2732bbbc7cd569922cb2dc00ff942961f9fcee26..670be76fcacc8c92cb46309ccea5179ba81f9801 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
@@ -12,8 +12,7 @@ import org.briarproject.util.StringUtils;
 public class ModemPluginFactory implements DuplexPluginFactory {
 
 	private static final int MAX_FRAME_LENGTH = 1024;
-	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
-	private static final long POLLING_INTERVAL = 60 * 60 * 1000; // 1 hour
+	private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
 
 	private final ModemFactory modemFactory;
 	private final SerialPortList serialPortList;
@@ -33,6 +32,6 @@ public class ModemPluginFactory implements DuplexPluginFactory {
 		String enabled = callback.getConfig().get("enabled");
 		if(StringUtils.isNullOrEmpty(enabled)) return null;
 		return new ModemPlugin(modemFactory, serialPortList, callback,
-				MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
+				MAX_FRAME_LENGTH, MAX_LATENCY);
 	}
 }
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
index 57855cfb6f8e8c6277ec501ca7acd720a99343e4..338b8c9577655a2f4c2dec5542c682a4fae77e7f 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentTest.java
@@ -75,6 +75,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 	protected final Message message, message1;
 	protected final TransportId transportId;
 	protected final TransportProperties transportProperties;
+	protected final int maxLatency;
 	protected final ContactId contactId;
 	protected final Contact contact;
 	protected final Endpoint endpoint;
@@ -102,6 +103,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		transportId = new TransportId("id");
 		transportProperties = new TransportProperties(Collections.singletonMap(
 				"bar", "baz"));
+		maxLatency = Integer.MAX_VALUE;
 		contactId = new ContactId(234);
 		contact = new Contact(contactId, author, localAuthorId);
 		endpoint = new Endpoint(contactId, transportId, 123, true);
@@ -691,11 +693,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getRawMessage(txn, messageId);
 			will(returnValue(raw));
 			oneOf(database).updateExpiryTime(txn, contactId, messageId,
-					Long.MAX_VALUE);
+					maxLatency);
 			oneOf(database).getRawMessage(txn, messageId1);
 			will(returnValue(raw1));
 			oneOf(database).updateExpiryTime(txn, contactId, messageId1,
-					Long.MAX_VALUE);
+					maxLatency);
 			oneOf(database).lowerRequestedFlag(txn, contactId, ids);
 			oneOf(database).commitTransaction(txn);
 		}});
@@ -703,7 +705,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 				eventBus, shutdown);
 
 		assertEquals(messages, db.generateBatch(contactId, size * 2,
-				Long.MAX_VALUE));
+				maxLatency));
 
 		context.assertIsSatisfied();
 	}
@@ -726,15 +728,15 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getMessagesToOffer(txn, contactId, 123);
 			will(returnValue(ids));
 			oneOf(database).updateExpiryTime(txn, contactId, messageId,
-					Long.MAX_VALUE);
+					maxLatency);
 			oneOf(database).updateExpiryTime(txn, contactId, messageId1,
-					Long.MAX_VALUE);
+					maxLatency);
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				eventBus, shutdown);
 
-		Offer o = db.generateOffer(contactId, 123, Long.MAX_VALUE);
+		Offer o = db.generateOffer(contactId, 123, maxLatency);
 		assertEquals(ids, o.getMessageIds());
 
 		context.assertIsSatisfied();
@@ -791,11 +793,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getRawMessage(txn, messageId);
 			will(returnValue(raw));
 			oneOf(database).updateExpiryTime(txn, contactId, messageId,
-					Long.MAX_VALUE);
+					maxLatency);
 			oneOf(database).getRawMessage(txn, messageId1);
 			will(returnValue(raw1));
 			oneOf(database).updateExpiryTime(txn, contactId, messageId1,
-					Long.MAX_VALUE);
+					maxLatency);
 			oneOf(database).lowerRequestedFlag(txn, contactId, ids);
 			oneOf(database).commitTransaction(txn);
 		}});
@@ -803,7 +805,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 				eventBus, shutdown);
 
 		assertEquals(messages, db.generateRequestedBatch(contactId, size * 2,
-				Long.MAX_VALUE));
+				maxLatency));
 
 		context.assertIsSatisfied();
 	}
@@ -821,14 +823,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getRetentionUpdate(txn, contactId, Long.MAX_VALUE);
+			oneOf(database).getRetentionUpdate(txn, contactId, maxLatency);
 			will(returnValue(null));
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				eventBus, shutdown);
 
-		assertNull(db.generateRetentionUpdate(contactId, Long.MAX_VALUE));
+		assertNull(db.generateRetentionUpdate(contactId, maxLatency));
 
 		context.assertIsSatisfied();
 	}
@@ -846,15 +848,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getRetentionUpdate(txn, contactId, Long.MAX_VALUE);
+			oneOf(database).getRetentionUpdate(txn, contactId, maxLatency);
 			will(returnValue(new RetentionUpdate(0, 1)));
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				eventBus, shutdown);
 
-		RetentionUpdate u = db.generateRetentionUpdate(contactId,
-				Long.MAX_VALUE);
+		RetentionUpdate u = db.generateRetentionUpdate(contactId, maxLatency);
 		assertEquals(0, u.getRetentionTime());
 		assertEquals(1, u.getVersion());
 
@@ -874,15 +875,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getSubscriptionUpdate(txn, contactId,
-					Long.MAX_VALUE);
+			oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
 			will(returnValue(null));
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				eventBus, shutdown);
 
-		assertNull(db.generateSubscriptionUpdate(contactId, Long.MAX_VALUE));
+		assertNull(db.generateSubscriptionUpdate(contactId, maxLatency));
 
 		context.assertIsSatisfied();
 	}
@@ -900,8 +900,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getSubscriptionUpdate(txn, contactId,
-					Long.MAX_VALUE);
+			oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
 			will(returnValue(new SubscriptionUpdate(Arrays.asList(group), 1)));
 			oneOf(database).commitTransaction(txn);
 		}});
@@ -909,7 +908,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 				eventBus, shutdown);
 
 		SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId,
-				Long.MAX_VALUE);
+				maxLatency);
 		assertEquals(Arrays.asList(group), u.getGroups());
 		assertEquals(1, u.getVersion());
 
@@ -929,14 +928,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getTransportUpdates(txn, contactId, Long.MAX_VALUE);
+			oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
 			will(returnValue(null));
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				eventBus, shutdown);
 
-		assertNull(db.generateTransportUpdates(contactId, Long.MAX_VALUE));
+		assertNull(db.generateTransportUpdates(contactId, maxLatency));
 
 		context.assertIsSatisfied();
 	}
@@ -954,7 +953,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(txn));
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getTransportUpdates(txn, contactId, Long.MAX_VALUE);
+			oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
 			will(returnValue(Arrays.asList(new TransportUpdate(transportId,
 					transportProperties, 1))));
 			oneOf(database).commitTransaction(txn);
@@ -963,7 +962,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 				eventBus, shutdown);
 
 		Collection<TransportUpdate> updates =
-				db.generateTransportUpdates(contactId, Long.MAX_VALUE);
+				db.generateTransportUpdates(contactId, maxLatency);
 		assertNotNull(updates);
 		assertEquals(1, updates.size());
 		TransportUpdate u = updates.iterator().next();
diff --git a/briar-tests/src/org/briarproject/db/ExponentialBackoffTest.java b/briar-tests/src/org/briarproject/db/ExponentialBackoffTest.java
index 923939e304409933a61520588f800dfdc94aa35a..f758e8c9e0c0be5ab1150d9f8cf58d44be5a2cd7 100644
--- a/briar-tests/src/org/briarproject/db/ExponentialBackoffTest.java
+++ b/briar-tests/src/org/briarproject/db/ExponentialBackoffTest.java
@@ -32,32 +32,30 @@ public class ExponentialBackoffTest extends BriarTestCase {
 		assertEquals(now, fromNow - fromZero);
 	}
 
-	@Test
-	public void testRoundTripTimeOverflow() {
-		long maxLatency = Long.MAX_VALUE / 2 + 1; // RTT will overflow
-		long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
-		assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
-	}
-
 	@Test
 	public void testTransmissionCountOverflow() {
-		long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
+		int maxLatency = Integer.MAX_VALUE; // RTT will not overflow
 		long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
-		assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
-		expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 1);
+		assertEquals(Integer.MAX_VALUE * 2L, expiry); // No overflow
+		expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 31);
+		assertEquals(Integer.MAX_VALUE * (2L << 31), expiry); // No overflow
+		expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 32);
 		assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
-		expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 2);
+		expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 33);
 		assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
 	}
 
 	@Test
 	public void testCurrentTimeOverflow() {
-		long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
-		long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
+		int maxLatency = Integer.MAX_VALUE; // RTT will not overflow
+		long now = Long.MAX_VALUE - (Integer.MAX_VALUE * (2L << 31));
+		long expiry = ExponentialBackoff.calculateExpiry(now, maxLatency, 0);
+		assertEquals(now + Integer.MAX_VALUE * 2L, expiry); // No overflow
+		expiry = ExponentialBackoff.calculateExpiry(now - 1, maxLatency, 31);
 		assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
-		expiry = ExponentialBackoff.calculateExpiry(1, maxLatency, 0);
+		expiry = ExponentialBackoff.calculateExpiry(now, maxLatency, 31);
 		assertEquals(Long.MAX_VALUE, expiry); // No overflow
-		expiry = ExponentialBackoff.calculateExpiry(2, maxLatency, 0);
+		expiry = ExponentialBackoff.calculateExpiry(now + 1, maxLatency, 32);
 		assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
 	}
 }
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index 3ecaf692a1501f0bc085b8d2c25c463bfce7828c..b567c2778ca97c8473bed2d2a2ba5b5d486f59f2 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -381,7 +381,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertTrue(it.hasNext());
 		assertEquals(messageId, it.next());
 		assertFalse(it.hasNext());
-		db.updateExpiryTime(txn, contactId, messageId, Long.MAX_VALUE);
+		db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE);
 
 		// The message should no longer be sendable
 		it = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE).iterator();
@@ -1109,7 +1109,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	@Test
 	public void testTemporarySecrets() throws Exception {
 		// Create an endpoint and four consecutive temporary secrets
-		long epoch = 123, latency = 234;
+		long epoch = 123;
+		int latency = 234;
 		boolean alice = false;
 		long outgoing1 = 345, centre1 = 456;
 		long outgoing2 = 567, centre2 = 678;
@@ -1235,7 +1236,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	@Test
 	public void testIncrementStreamCounter() throws Exception {
 		// Create an endpoint and a temporary secret
-		long epoch = 123, latency = 234;
+		long epoch = 123;
+		int latency = 234;
 		boolean alice = false;
 		long period = 345, outgoing = 456, centre = 567;
 		Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
@@ -1290,7 +1292,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	@Test
 	public void testSetReorderingWindow() throws Exception {
 		// Create an endpoint and a temporary secret
-		long epoch = 123, latency = 234;
+		long epoch = 123;
+		int latency = 234;
 		boolean alice = false;
 		long period = 345, outgoing = 456, centre = 567;
 		Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
@@ -1359,8 +1362,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	@Test
 	public void testEndpoints() throws Exception {
 		// Create some endpoints
-		long epoch1 = 123, latency1 = 234;
-		long epoch2 = 345, latency2 = 456;
+		long epoch1 = 123, epoch2 = 234;
+		int latency1 = 345, latency2 = 456;
 		boolean alice1 = true, alice2 = false;
 		TransportId transportId1 = new TransportId("bar");
 		TransportId transportId2 = new TransportId("baz");
diff --git a/briar-tests/src/org/briarproject/messaging/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/messaging/SimplexMessagingIntegrationTest.java
index 4d5b25767efae6b90f9a055a6d6f99eac920aba9..7a24f0c94141d812b1f80eef88d52744bd79feb2 100644
--- a/briar-tests/src/org/briarproject/messaging/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/messaging/SimplexMessagingIntegrationTest.java
@@ -2,6 +2,7 @@ package org.briarproject.messaging;
 
 import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
 import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
 
@@ -32,7 +33,9 @@ import org.briarproject.api.messaging.Message;
 import org.briarproject.api.messaging.MessageFactory;
 import org.briarproject.api.messaging.MessageVerifier;
 import org.briarproject.api.messaging.MessagingSession;
+import org.briarproject.api.messaging.PacketReader;
 import org.briarproject.api.messaging.PacketReaderFactory;
+import org.briarproject.api.messaging.PacketWriter;
 import org.briarproject.api.messaging.PacketWriterFactory;
 import org.briarproject.api.transport.Endpoint;
 import org.briarproject.api.transport.StreamContext;
@@ -56,8 +59,9 @@ import com.google.inject.Injector;
 
 public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
-	private static final long CLOCK_DIFFERENCE = 60 * 1000;
-	private static final long LATENCY = 60 * 1000;
+	private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
+	private static final int ROTATION_PERIOD =
+			MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
 
 	private final File testDir = TestUtils.getTestDirectory();
 	private final File aliceDir = new File(testDir, "alice");
@@ -73,8 +77,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Create matching secrets for Alice and Bob
 		initialSecret = new byte[32];
 		new Random().nextBytes(initialSecret);
-		long rotationPeriod = 2 * CLOCK_DIFFERENCE + LATENCY;
-		epoch = System.currentTimeMillis() - 2 * rotationPeriod;
+		epoch = System.currentTimeMillis() - 2 * ROTATION_PERIOD;
 	}
 
 	@Override
@@ -121,10 +124,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		db.addGroup(group);
 		db.setInboxGroup(contactId, group);
 		// Add the transport and the endpoint
-		db.addTransport(transportId, LATENCY);
+		db.addTransport(transportId, MAX_LATENCY);
 		Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
 		db.addEndpoint(ep);
-		keyManager.endpointAdded(ep, LATENCY, initialSecret.clone());
+		keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
 		// Send Bob a message
 		String contentType = "text/plain";
 		long timestamp = System.currentTimeMillis();
@@ -145,10 +148,11 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		EventBus eventBus = alice.getInstance(EventBus.class);
 		PacketWriterFactory packetWriterFactory =
 				alice.getInstance(PacketWriterFactory.class);
-		MessagingSession session = new SimplexOutgoingSession(db,
-				new ImmediateExecutor(), eventBus, packetWriterFactory,
-				contactId, transportId, Long.MAX_VALUE,
+		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
 				streamWriter.getOutputStream());
+		MessagingSession session = new SimplexOutgoingSession(db,
+				new ImmediateExecutor(), eventBus, contactId, transportId,
+				MAX_LATENCY, packetWriter);
 		// Write whatever needs to be written
 		session.run();
 		streamWriter.getOutputStream().close();
@@ -182,10 +186,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		db.addGroup(group);
 		db.setInboxGroup(contactId, group);
 		// Add the transport and the endpoint
-		db.addTransport(transportId, LATENCY);
+		db.addTransport(transportId, MAX_LATENCY);
 		Endpoint ep = new Endpoint(contactId, transportId, epoch, false);
 		db.addEndpoint(ep);
-		keyManager.endpointAdded(ep, LATENCY, initialSecret.clone());
+		keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret.clone());
 		// Set up an event listener
 		MessageListener listener = new MessageListener();
 		bob.getInstance(EventBus.class).addListener(listener);
@@ -208,10 +212,11 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				bob.getInstance(MessageVerifier.class);
 		PacketReaderFactory packetReaderFactory =
 				bob.getInstance(PacketReaderFactory.class);
+		PacketReader packetReader = packetReaderFactory.createPacketReader(
+				streamReader.getInputStream());
 		MessagingSession session = new IncomingSession(db,
 				new ImmediateExecutor(), new ImmediateExecutor(), eventBus,
-				messageVerifier, packetReaderFactory, contactId, transportId,
-				streamReader.getInputStream());
+				messageVerifier, contactId, transportId, packetReader);
 		// No messages should have been added yet
 		assertFalse(listener.messageAdded);
 		// Read whatever needs to be read
diff --git a/briar-tests/src/org/briarproject/messaging/SimplexOutgoingSessionTest.java b/briar-tests/src/org/briarproject/messaging/SimplexOutgoingSessionTest.java
index f9fa68c6fdd0efef239201b32a131848093983de..fa13af51024f6cdc9938635c41ff3def51032182 100644
--- a/briar-tests/src/org/briarproject/messaging/SimplexOutgoingSessionTest.java
+++ b/briar-tests/src/org/briarproject/messaging/SimplexOutgoingSessionTest.java
@@ -1,72 +1,53 @@
 package org.briarproject.messaging;
 
-import java.io.ByteArrayOutputStream;
 import java.util.Arrays;
-import java.util.Random;
 import java.util.concurrent.Executor;
 
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestUtils;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.TransportId;
-import org.briarproject.api.UniqueId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.messaging.Ack;
 import org.briarproject.api.messaging.MessageId;
-import org.briarproject.api.messaging.PacketWriterFactory;
+import org.briarproject.api.messaging.PacketWriter;
 import org.briarproject.plugins.ImmediateExecutor;
-import org.briarproject.serial.SerialModule;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.junit.Test;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Module;
-
 public class SimplexOutgoingSessionTest extends BriarTestCase {
 
-	// FIXME: This is an integration test, not a unit test
+	private static final int MAX_MESSAGES_PER_ACK = 10;
 
 	private final Mockery context;
 	private final DatabaseComponent db;
 	private final Executor dbExecutor;
 	private final EventBus eventBus;
-	private final PacketWriterFactory packetWriterFactory;
 	private final ContactId contactId;
 	private final TransportId transportId;
 	private final MessageId messageId;
-	private final byte[] secret;
+	private final int maxLatency;
+	private final PacketWriter packetWriter;
 
 	public SimplexOutgoingSessionTest() {
 		context = new Mockery();
 		db = context.mock(DatabaseComponent.class);
 		dbExecutor = new ImmediateExecutor();
-		Module testModule = new AbstractModule() {
-			@Override
-			public void configure() {
-				bind(PacketWriterFactory.class).to(
-						PacketWriterFactoryImpl.class);
-			}
-		};
-		Injector i = Guice.createInjector(testModule, new SerialModule());
 		eventBus = context.mock(EventBus.class);
-		packetWriterFactory = i.getInstance(PacketWriterFactory.class);
+		packetWriter = context.mock(PacketWriter.class);
 		contactId = new ContactId(234);
 		transportId = new TransportId("id");
 		messageId = new MessageId(TestUtils.getRandomId());
-		secret = new byte[32];
-		new Random().nextBytes(secret);
+		maxLatency = Integer.MAX_VALUE;
 	}
 
 	@Test
 	public void testNothingToSend() throws Exception {
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
-				dbExecutor, eventBus, packetWriterFactory, contactId,
-				transportId, Long.MAX_VALUE, out);
+				dbExecutor, eventBus, contactId, transportId, maxLatency,
+				packetWriter);
 		context.checking(new Expectations() {{
 			// Add listener
 			oneOf(eventBus).addListener(session);
@@ -74,46 +55,45 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 			oneOf(db).generateTransportAcks(contactId);
 			will(returnValue(null));
 			// No transport updates to send
-			oneOf(db).generateTransportUpdates(with(contactId),
-					with(any(long.class)));
+			oneOf(db).generateTransportUpdates(contactId, maxLatency);
 			will(returnValue(null));
 			// No subscription ack to send
 			oneOf(db).generateSubscriptionAck(contactId);
 			will(returnValue(null));
 			// No subscription update to send
-			oneOf(db).generateSubscriptionUpdate(with(contactId),
-					with(any(long.class)));
+			oneOf(db).generateSubscriptionUpdate(contactId, maxLatency);
 			will(returnValue(null));
 			// No retention ack to send
 			oneOf(db).generateRetentionAck(contactId);
 			will(returnValue(null));
 			// No retention update to send
-			oneOf(db).generateRetentionUpdate(with(contactId),
-					with(any(long.class)));
+			oneOf(db).generateRetentionUpdate(contactId, maxLatency);
 			will(returnValue(null));
 			// No acks to send
-			oneOf(db).generateAck(with(contactId), with(any(int.class)));
+			oneOf(packetWriter).getMaxMessagesForAck(with(any(long.class)));
+			will(returnValue(MAX_MESSAGES_PER_ACK));
+			oneOf(db).generateAck(contactId, MAX_MESSAGES_PER_ACK);
 			will(returnValue(null));
 			// No messages to send
 			oneOf(db).generateBatch(with(contactId), with(any(int.class)),
-					with(any(long.class)));
+					with(maxLatency));
 			will(returnValue(null));
+			// Flush the output stream
+			oneOf(packetWriter).flush();
 			// Remove listener
 			oneOf(eventBus).removeListener(session);
 		}});
 		session.run();
-		// Nothing should have been written
-		assertEquals(0, out.size());
 		context.assertIsSatisfied();
 	}
 
 	@Test
 	public void testSomethingToSend() throws Exception {
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
-				dbExecutor, eventBus, packetWriterFactory, contactId,
-				transportId, Long.MAX_VALUE, out);
+		final Ack ack = new Ack(Arrays.asList(messageId));
 		final byte[] raw = new byte[1234];
+		final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
+				dbExecutor, eventBus, contactId, transportId, maxLatency,
+				packetWriter);
 		context.checking(new Expectations() {{
 			// Add listener
 			oneOf(eventBus).addListener(session);
@@ -121,43 +101,46 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
 			oneOf(db).generateTransportAcks(contactId);
 			will(returnValue(null));
 			// No transport updates to send
-			oneOf(db).generateTransportUpdates(with(contactId),
-					with(any(long.class)));
+			oneOf(db).generateTransportUpdates(contactId, maxLatency);
 			will(returnValue(null));
 			// No subscription ack to send
 			oneOf(db).generateSubscriptionAck(contactId);
 			will(returnValue(null));
 			// No subscription update to send
-			oneOf(db).generateSubscriptionUpdate(with(contactId),
-					with(any(long.class)));
+			oneOf(db).generateSubscriptionUpdate(contactId, maxLatency);
 			will(returnValue(null));
 			// No retention ack to send
 			oneOf(db).generateRetentionAck(contactId);
 			will(returnValue(null));
 			// No retention update to send
-			oneOf(db).generateRetentionUpdate(with(contactId),
-					with(any(long.class)));
+			oneOf(db).generateRetentionUpdate(contactId, maxLatency);
 			will(returnValue(null));
 			// One ack to send
-			oneOf(db).generateAck(with(contactId), with(any(int.class)));
-			will(returnValue(new Ack(Arrays.asList(messageId))));
+			oneOf(packetWriter).getMaxMessagesForAck(with(any(long.class)));
+			will(returnValue(MAX_MESSAGES_PER_ACK));
+			oneOf(db).generateAck(contactId, MAX_MESSAGES_PER_ACK);
+			will(returnValue(ack));
+			oneOf(packetWriter).writeAck(ack);
 			// No more acks
-			oneOf(db).generateAck(with(contactId), with(any(int.class)));
+			oneOf(packetWriter).getMaxMessagesForAck(with(any(long.class)));
+			will(returnValue(MAX_MESSAGES_PER_ACK));
+			oneOf(db).generateAck(contactId, MAX_MESSAGES_PER_ACK);
 			will(returnValue(null));
 			// One message to send
 			oneOf(db).generateBatch(with(contactId), with(any(int.class)),
-					with(any(long.class)));
+					with(maxLatency));
 			will(returnValue(Arrays.asList(raw)));
+			oneOf(packetWriter).writeMessage(raw);
 			// No more messages
 			oneOf(db).generateBatch(with(contactId), with(any(int.class)),
-					with(any(long.class)));
+					with(maxLatency));
 			will(returnValue(null));
+			// Flush the output stream
+			oneOf(packetWriter).flush();
 			// Remove listener
 			oneOf(eventBus).removeListener(session);
 		}});
 		session.run();
-		// Something should have been written
-		assertTrue(out.size() > UniqueId.LENGTH + raw.length);
 		context.assertIsSatisfied();
 	}
 }
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index 2e088fa3066b09da872c8c1adb5fb9db60ed8efa..8dab883cb74cade22f78f7e63d188c9156a2d5e8 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -44,19 +44,19 @@ public class PluginManagerImplTest extends BriarTestCase {
 				context.mock(SimplexPluginFactory.class);
 		final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
 		final TransportId simplexId = new TransportId("simplex");
-		final long simplexLatency = 12345;
+		final int simplexLatency = 12345;
 		final SimplexPluginFactory simplexFailFactory =
 				context.mock(SimplexPluginFactory.class, "simplexFailFactory");
 		final SimplexPlugin simplexFailPlugin =
 				context.mock(SimplexPlugin.class, "simplexFailPlugin");
 		final TransportId simplexFailId = new TransportId("simplex1");
-		final long simplexFailLatency = 23456;
+		final int simplexFailLatency = 23456;
 		// Two duplex plugin factories: one creates a plugin, the other fails
 		final DuplexPluginFactory duplexFactory =
 				context.mock(DuplexPluginFactory.class);
 		final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
 		final TransportId duplexId = new TransportId("duplex");
-		final long duplexLatency = 34567;
+		final int duplexLatency = 34567;
 		final DuplexPluginFactory duplexFailFactory =
 				context.mock(DuplexPluginFactory.class, "duplexFailFactory");
 		final TransportId duplexFailId = new TransportId("duplex1");
diff --git a/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java b/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
index 3371bd7e0dfe715f69cbaf55c3bfef7e4ff2afbb..3aa5dab235858e3c1295873284ed2b1f52e977f3 100644
--- a/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
+++ b/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
@@ -24,7 +24,7 @@ public class ModemPluginTest extends BriarTestCase {
 		final SerialPortList serialPortList =
 				context.mock(SerialPortList.class);
 		final ModemPlugin plugin = new ModemPlugin(modemFactory,
-				serialPortList, null, 0, 0, 0);
+				serialPortList, null, 0, 0);
 		final Modem modem = context.mock(Modem.class);
 		context.checking(new Expectations() {{
 			oneOf(serialPortList).getPortNames();
@@ -58,7 +58,7 @@ public class ModemPluginTest extends BriarTestCase {
 		final DuplexPluginCallback callback =
 				context.mock(DuplexPluginCallback.class);
 		final ModemPlugin plugin = new ModemPlugin(modemFactory,
-				serialPortList, callback, 0, 0, 0);
+				serialPortList, callback, 0, 0);
 		final Modem modem = context.mock(Modem.class);
 		final TransportProperties local = new TransportProperties();
 		local.put("iso3166", ISO_1336);
@@ -99,7 +99,7 @@ public class ModemPluginTest extends BriarTestCase {
 		final DuplexPluginCallback callback =
 				context.mock(DuplexPluginCallback.class);
 		final ModemPlugin plugin = new ModemPlugin(modemFactory,
-				serialPortList, callback, 0, 0, 0);
+				serialPortList, callback, 0, 0);
 		final Modem modem = context.mock(Modem.class);
 		final TransportProperties local = new TransportProperties();
 		local.put("iso3166", ISO_1336);
@@ -140,7 +140,7 @@ public class ModemPluginTest extends BriarTestCase {
 		final DuplexPluginCallback callback =
 				context.mock(DuplexPluginCallback.class);
 		final ModemPlugin plugin = new ModemPlugin(modemFactory,
-				serialPortList, callback, 0, 0, 0);
+				serialPortList, callback, 0, 0);
 		final Modem modem = context.mock(Modem.class);
 		final TransportProperties local = new TransportProperties();
 		local.put("iso3166", ISO_1336);
diff --git a/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java b/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java
index f11f11f154260870fa75d7fa5afe93224497394f..c160b59db3dfdd6498ea96a8f8e767a6eb041e05 100644
--- a/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/transport/KeyManagerImplTest.java
@@ -26,9 +26,9 @@ import org.junit.Test;
 public class KeyManagerImplTest extends BriarTestCase {
 
 	private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
-	private static final long MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
-	private static final long ROTATION_PERIOD_LENGTH =
-			MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
+	private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
+	private static final int ROTATION_PERIOD =
+			MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
 
 	private final ContactId contactId;
 	private final TransportId transportId;
@@ -294,7 +294,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 					MAX_LATENCY)));
 			// The current time is the start of period 2
 			oneOf(clock).currentTimeMillis();
-			will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH));
+			will(returnValue(EPOCH + ROTATION_PERIOD));
 			// The secret for period 3 should be derived and stored
 			oneOf(crypto).deriveNextSecret(secret0, 1);
 			will(returnValue(secret1.clone()));
@@ -353,7 +353,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 					MAX_LATENCY)));
 			// The current time is the end of period 3
 			oneOf(clock).currentTimeMillis();
-			will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
+			will(returnValue(EPOCH + 3 * ROTATION_PERIOD - 1));
 			// The secrets for periods 3 and 4 should be derived from secret 1
 			oneOf(crypto).deriveNextSecret(secret1, 2);
 			will(returnValue(secret2.clone()));
@@ -484,7 +484,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 					with(any(long.class)), with(any(long.class)));
 			// run() during period 2: the secrets should be rotated
 			oneOf(clock).currentTimeMillis();
-			will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH + 1));
+			will(returnValue(EPOCH + ROTATION_PERIOD + 1));
 			oneOf(crypto).deriveNextSecret(secret0, 1);
 			will(returnValue(secret1.clone()));
 			oneOf(crypto).deriveNextSecret(secret1, 2);
@@ -559,7 +559,7 @@ public class KeyManagerImplTest extends BriarTestCase {
 					with(any(long.class)), with(any(long.class)));
 			// run() during period 3 (late): the secrets should be rotated
 			oneOf(clock).currentTimeMillis();
-			will(returnValue(EPOCH + 2 * ROTATION_PERIOD_LENGTH + 1));
+			will(returnValue(EPOCH + 2 * ROTATION_PERIOD + 1));
 			oneOf(crypto).deriveNextSecret(secret1, 2);
 			will(returnValue(secret2.clone()));
 			oneOf(crypto).deriveNextSecret(secret2, 3);
diff --git a/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java b/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java
index 1207f9363e85b7042050a0cc2f8779148c391cb1..70e8cfea3c9b2039873d833afe10e2f2d993640a 100644
--- a/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/transport/KeyRotationIntegrationTest.java
@@ -32,9 +32,9 @@ import org.junit.Test;
 public class KeyRotationIntegrationTest extends BriarTestCase {
 
 	private static final long EPOCH = 1000L * 1000L * 1000L * 1000L;
-	private static final long MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
-	private static final long ROTATION_PERIOD_LENGTH =
-			MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
+	private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
+	private static final int ROTATION_PERIOD =
+			MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
 
 	private final ContactId contactId;
 	private final TransportId transportId;
@@ -653,7 +653,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 					MAX_LATENCY)));
 			// The current time is the start of period 2
 			oneOf(clock).currentTimeMillis();
-			will(returnValue(EPOCH + ROTATION_PERIOD_LENGTH));
+			will(returnValue(EPOCH + ROTATION_PERIOD));
 			// The secret for period 3 should be derived and stored
 			oneOf(crypto).deriveNextSecret(secret0, 1);
 			will(returnValue(secret1.clone()));
@@ -778,7 +778,7 @@ public class KeyRotationIntegrationTest extends BriarTestCase {
 					MAX_LATENCY)));
 			// The current time is the end of period 3
 			oneOf(clock).currentTimeMillis();
-			will(returnValue(EPOCH + 3 * ROTATION_PERIOD_LENGTH - 1));
+			will(returnValue(EPOCH + 3 * ROTATION_PERIOD - 1));
 			// The secrets for periods 3 and 4 should be derived from secret 1
 			oneOf(crypto).deriveNextSecret(secret1, 2);
 			will(returnValue(secret2.clone()));