diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index 80df25c16b6724a5c8bad31f6a352e2e1dcd7ccd..993dea7785070cf677fd44f8c80d249d8552a30f 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -80,23 +80,25 @@ public interface DatabaseComponent {
 
 	/**
 	 * Generates a batch of raw messages for the given contact, with a total
-	 * length less than or equal to the given length. Returns null if
-	 * there are no sendable messages that fit in the given length.
+	 * length less than or equal to the given length, for transmission over a
+	 * transport with the given maximum latency. Returns null if there are no
+	 * sendable messages that fit in the given length.
 	 */
-	Collection<byte[]> generateBatch(ContactId c, int maxLength)
-			throws DbException;
+	Collection<byte[]> generateBatch(ContactId c, int maxLength,
+			long maxLatency) throws DbException;
 
 	/**
 	 * Generates a batch of raw messages for the given contact from the given
 	 * collection of requested messages, with a total length less than or equal
-	 * to the given length. Any messages that were either added to the batch,
-	 * or were considered but are no longer sendable to the contact, are
-	 * removed from the collection of requested messages before returning.
-	 * Returns null if there are no sendable messages that fit in the given
-	 * length.
+	 * to the given length, for transmission over a transport with the given
+	 * maximum latency. Any messages that were either added to the batch, or
+	 * were considered but are no longer sendable to the contact, are removed
+	 * from the collection of requested messages before returning. Returns null
+	 * if there are no sendable messages that fit in the given length.
 	 */
 	Collection<byte[]> generateBatch(ContactId c, int maxLength,
-			Collection<MessageId> requested) throws DbException;
+			long maxLatency, Collection<MessageId> requested)
+					throws DbException;
 
 	/**
 	 * Generates an offer for the given contact. Returns null if there are no
diff --git a/briar-api/src/net/sf/briar/api/plugins/duplex/DuplexTransportConnection.java b/briar-api/src/net/sf/briar/api/plugins/duplex/DuplexTransportConnection.java
index 6fc1d3cd570b7602f0a1375cde124ed50bdc2fce..06e0f148bfcf4bccf3502e2a7904eab41d2a4545 100644
--- a/briar-api/src/net/sf/briar/api/plugins/duplex/DuplexTransportConnection.java
+++ b/briar-api/src/net/sf/briar/api/plugins/duplex/DuplexTransportConnection.java
@@ -11,6 +11,9 @@ import java.io.OutputStream;
  */
 public interface DuplexTransportConnection {
 
+	/** Returns the maximum latency of the transport in milliseconds. */
+	long getMaxLatency();
+
 	/** Returns an input stream for reading from the connection. */
 	InputStream getInputStream() throws IOException;
 
diff --git a/briar-api/src/net/sf/briar/api/plugins/simplex/SimplexTransportWriter.java b/briar-api/src/net/sf/briar/api/plugins/simplex/SimplexTransportWriter.java
index 152b7fd673e9d0402c5875007b49d2df90590dd9..facb4769c32d75bf80538d32ac14716a6e71a7d7 100644
--- a/briar-api/src/net/sf/briar/api/plugins/simplex/SimplexTransportWriter.java
+++ b/briar-api/src/net/sf/briar/api/plugins/simplex/SimplexTransportWriter.java
@@ -12,6 +12,9 @@ public interface SimplexTransportWriter {
 	/** Returns the capacity of the transport in bytes. */
 	long getCapacity();
 
+	/** Returns the maximum latency of the transport in milliseconds. */
+	long getMaxLatency();
+
 	/** Returns an output stream for writing to the transport. */
 	OutputStream getOutputStream() throws IOException;
 
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 4b8ff546e84ecd37ff4c24477457ef64272858f0..eeddfb96b4c594af83ab71b735f34c4a4c1f95c8 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -488,8 +488,8 @@ DatabaseCleaner.Callback {
 		return new Ack(acked);
 	}
 
-	public Collection<byte[]> generateBatch(ContactId c, int maxLength)
-			throws DbException {
+	public Collection<byte[]> generateBatch(ContactId c, int maxLength,
+			long maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
 		// Get some sendable messages from the database
@@ -504,9 +504,8 @@ DatabaseCleaner.Callback {
 						if(!db.containsContact(txn, c))
 							throw new NoSuchContactException();
 						ids = db.getSendableMessages(txn, c, maxLength);
-						for(MessageId m : ids) {
+						for(MessageId m : ids)
 							messages.add(db.getRawMessage(txn, m));
-						}
 						db.commitTransaction(txn);
 					} catch(DbException e) {
 						db.abortTransaction(txn);
@@ -519,13 +518,14 @@ DatabaseCleaner.Callback {
 				messageLock.readLock().unlock();
 			}
 			if(messages.isEmpty()) return null;
+			// Calculate the expiry time of the messages
+			long expiry = calculateExpiryTime(maxLatency);
 			// Record the messages as sent
 			messageLock.writeLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
-					// FIXME: Calculate the expiry time
-					db.addOutstandingMessages(txn, c, ids, Long.MAX_VALUE);
+					db.addOutstandingMessages(txn, c, ids, expiry);
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
@@ -541,7 +541,8 @@ DatabaseCleaner.Callback {
 	}
 
 	public Collection<byte[]> generateBatch(ContactId c, int maxLength,
-			Collection<MessageId> requested) throws DbException {
+			long maxLatency, Collection<MessageId> requested)
+					throws DbException {
 		Collection<MessageId> ids = new ArrayList<MessageId>();
 		List<byte[]> messages = new ArrayList<byte[]>();
 		// Get some sendable messages from the database
@@ -579,13 +580,14 @@ DatabaseCleaner.Callback {
 				messageLock.readLock().unlock();
 			}
 			if(messages.isEmpty()) return null;
+			// Calculate the expiry times of the messages
+			long expiry = calculateExpiryTime(maxLatency);
 			// Record the messages as sent
 			messageLock.writeLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
-					// FIXME: Calculate the expiry time
-					db.addOutstandingMessages(txn, c, ids, Long.MAX_VALUE);
+					db.addOutstandingMessages(txn, c, ids, expiry);
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
@@ -600,6 +602,14 @@ DatabaseCleaner.Callback {
 		return Collections.unmodifiableList(messages);
 	}
 
+	private long calculateExpiryTime(long maxLatency) {
+		long roundTrip = maxLatency * 2;
+		if(roundTrip < 0) roundTrip = Long.MAX_VALUE; // Overflow
+		long expiry = clock.currentTimeMillis() + roundTrip;
+		if(expiry < 0) expiry = Long.MAX_VALUE; // Overflow
+		return expiry;
+	}
+
 	public Offer generateOffer(ContactId c, int maxMessages)
 			throws DbException {
 		Collection<MessageId> offered;
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 596b7a8632100047227a1fdba213be7fc2f7dbc7..99970f9ab9d5f763de93a008537c79286987929b 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -156,7 +156,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " (messageId HASH NOT NULL,"
 					+ " contactId INT NOT NULL,"
 					+ " seen BOOLEAN NOT NULL,"
-					+ " transmissionCount INT NOT NULL,"
 					+ " expiry BIGINT NOT NULL,"
 					+ " PRIMARY KEY (messageId, contactId),"
 					+ " FOREIGN KEY (messageId)"
@@ -651,16 +650,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 			Collection<MessageId> sent, long expiry) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			// Update the transmission count and expiry time of each message
-			String sql = "UPDATE statuses SET expiry = ?,"
-					+ " transmissionCount = transmissionCount + ?"
+			// Update the expiry time of each message
+			String sql = "UPDATE statuses SET expiry = ?"
 					+ " WHERE messageId = ? AND contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setLong(1, expiry);
-			ps.setInt(2, 1);
-			ps.setInt(4, c.getInt());
+			ps.setInt(3, c.getInt());
 			for(MessageId m : sent) {
-				ps.setBytes(3, m.getBytes());
+				ps.setBytes(2, m.getBytes());
 				ps.addBatch();
 			}
 			int[] batchAffected = ps.executeBatch();
@@ -713,8 +710,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		try {
 			String sql = "INSERT INTO statuses"
-					+ " (messageId, contactId, seen, transmissionCount, expiry)"
-					+ " VALUES (?, ?, ?, ZERO(), ZERO())";
+					+ " (messageId, contactId, seen, expiry)"
+					+ " VALUES (?, ?, ?, ZERO())";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			ps.setInt(2, c.getInt());
diff --git a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
index 6d4984cef79825081d124351f8ad41076c654548..1f56449e743a7e1713d7cdaa40cfda0e1da7e93a 100644
--- a/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/duplex/DuplexConnection.java
@@ -467,7 +467,8 @@ abstract class DuplexConnection implements DatabaseListener {
 			assert writer != null;
 			try {
 				Collection<byte[]> batch = db.generateBatch(contactId,
-						Integer.MAX_VALUE, requested);
+						Integer.MAX_VALUE, transport.getMaxLatency(),
+						requested);
 				if(batch == null) new GenerateOffer().run();
 				else writerTasks.add(new WriteBatch(batch, requested));
 			} catch(DbException e) {
diff --git a/briar-core/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnection.java b/briar-core/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnection.java
index 4a643a123727bcb3d91dc156eed87e6772c0653f..e2990f547a2c46f1b49642a08fd12f8651a19f8b 100644
--- a/briar-core/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnection.java
+++ b/briar-core/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnection.java
@@ -69,6 +69,7 @@ class OutgoingSimplexConnection {
 				throw new EOFException();
 			PacketWriter writer = packetWriterFactory.createPacketWriter(out,
 					transport.shouldFlush());
+			long maxLatency = transport.getMaxLatency();
 			// Send the initial packets: updates and acks
 			boolean hasSpace = writeTransportAcks(conn, writer);
 			if(hasSpace) hasSpace = writeTransportUpdates(conn, writer);
@@ -89,12 +90,13 @@ class OutgoingSimplexConnection {
 			// Write messages until you can't write messages no more
 			capacity = conn.getRemainingCapacity();
 			int maxLength = (int) Math.min(capacity, MAX_PACKET_LENGTH);
-			Collection<byte[]> batch = db.generateBatch(contactId, maxLength);
+			Collection<byte[]> batch = db.generateBatch(contactId, maxLength,
+					maxLatency);
 			while(batch != null) {
 				for(byte[] raw : batch) writer.writeMessage(raw);
 				capacity = conn.getRemainingCapacity();
 				maxLength = (int) Math.min(capacity, MAX_PACKET_LENGTH);
-				batch = db.generateBatch(contactId, maxLength);
+				batch = db.generateBatch(contactId, maxLength, maxLatency);
 			}
 			writer.flush();
 			writer.close();
diff --git a/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
index 78734a0e3bda1e2075d0a4f0a57a12632f416956..639afb122b23b1ea96a5ca3756389c8341039f9e 100644
--- a/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
@@ -155,7 +155,7 @@ class BluetoothPlugin implements DuplexPlugin {
 				return;
 			}
 			BluetoothTransportConnection conn =
-					new BluetoothTransportConnection(s);
+					new BluetoothTransportConnection(s, maxLatency);
 			callback.incomingConnectionCreated(conn);
 			if(!running) return;
 		}
@@ -202,7 +202,7 @@ class BluetoothPlugin implements DuplexPlugin {
 	private DuplexTransportConnection connect(String url) {
 		try {
 			StreamConnection s = (StreamConnection) Connector.open(url);
-			return new BluetoothTransportConnection(s);
+			return new BluetoothTransportConnection(s, maxLatency);
 		} catch(IOException e) {
 			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			return null;
@@ -294,7 +294,7 @@ class BluetoothPlugin implements DuplexPlugin {
 		// Try to accept a connection and close the socket
 		try {
 			StreamConnection s = scn.acceptAndOpen();
-			return new BluetoothTransportConnection(s);
+			return new BluetoothTransportConnection(s, maxLatency);
 		} catch(IOException e) {
 			// This is expected when the socket is closed
 			if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
diff --git a/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothTransportConnection.java b/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothTransportConnection.java
index bc2c1fbd44858ac027a0d29b1fddda567cf9ea35..5d5fda3b95da2d5faa31014ce869bc571b135635 100644
--- a/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothTransportConnection.java
+++ b/briar-core/src/net/sf/briar/plugins/bluetooth/BluetoothTransportConnection.java
@@ -11,9 +11,15 @@ import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
 class BluetoothTransportConnection implements DuplexTransportConnection {
 
 	private final StreamConnection stream;
+	private final long maxLatency;
 
-	BluetoothTransportConnection(StreamConnection stream) {
+	BluetoothTransportConnection(StreamConnection stream, long maxLatency) {
 		this.stream = stream;
+		this.maxLatency = maxLatency;
+	}
+
+	public long getMaxLatency() {
+		return maxLatency;
 	}
 
 	public InputStream getInputStream() throws IOException {
diff --git a/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java b/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
index a20da57a9fe41cac5a7137c14299c86d24eae286..275f968c153a19d681094169087a2f82e919c3a3 100644
--- a/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothPlugin.java
@@ -188,7 +188,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 				return;
 			}
 			DroidtoothTransportConnection conn =
-					new DroidtoothTransportConnection(s);
+					new DroidtoothTransportConnection(s, maxLatency);
 			callback.incomingConnectionCreated(conn);
 			if(!running) return;
 		}
@@ -250,7 +250,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 		try {
 			BluetoothSocket s = InsecureBluetooth.createSocket(d, u);
 			s.connect();
-			return new DroidtoothTransportConnection(s);
+			return new DroidtoothTransportConnection(s, maxLatency);
 		} catch(IOException e) {
 			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			return null;
@@ -310,7 +310,8 @@ class DroidtoothPlugin implements DuplexPlugin {
 		}
 		// Return the first connection received by the socket, if any
 		try {
-			return new DroidtoothTransportConnection(ss.accept((int) timeout));
+			BluetoothSocket s = ss.accept((int) timeout);
+			return new DroidtoothTransportConnection(s, maxLatency);
 		} catch(SocketTimeoutException e) {
 			if(LOG.isLoggable(INFO)) LOG.info("Invitation timed out");
 			return null;
diff --git a/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java b/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java
index c493a5b6711d20cc8a500a6da1f3b74b5cde3e09..2a3238033b6394548570f74d0519035e7651ec5d 100644
--- a/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java
+++ b/briar-core/src/net/sf/briar/plugins/droidtooth/DroidtoothTransportConnection.java
@@ -10,9 +10,15 @@ import android.bluetooth.BluetoothSocket;
 class DroidtoothTransportConnection implements DuplexTransportConnection {
 
 	private final BluetoothSocket socket;
+	private final long maxLatency;
 
-	DroidtoothTransportConnection(BluetoothSocket socket) {
+	DroidtoothTransportConnection(BluetoothSocket socket, long maxLatency) {
 		this.socket = socket;
+		this.maxLatency = maxLatency;
+	}
+
+	public long getMaxLatency() {
+		return maxLatency;
 	}
 
 	public InputStream getInputStream() throws IOException {
diff --git a/briar-core/src/net/sf/briar/plugins/file/FilePlugin.java b/briar-core/src/net/sf/briar/plugins/file/FilePlugin.java
index 11e55d127996024660b7d35e216ae66731ceeec2..d72d4ee8ba796e5759b8e2b61905f7f1db35ff66 100644
--- a/briar-core/src/net/sf/briar/plugins/file/FilePlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/file/FilePlugin.java
@@ -28,6 +28,7 @@ public abstract class FilePlugin implements SimplexPlugin {
 
 	protected final Executor pluginExecutor;
 	protected final SimplexPluginCallback callback;
+	protected final long maxLatency;
 
 	protected volatile boolean running = false;
 
@@ -37,9 +38,10 @@ public abstract class FilePlugin implements SimplexPlugin {
 	protected abstract void readerFinished(File f);
 
 	protected FilePlugin(@PluginExecutor Executor pluginExecutor,
-			SimplexPluginCallback callback) {
+			SimplexPluginCallback callback, long maxLatency) {
 		this.pluginExecutor = pluginExecutor;
 		this.callback = callback;
+		this.maxLatency = maxLatency;
 	}
 
 	public SimplexTransportReader createReader(ContactId c) {
@@ -72,7 +74,7 @@ public abstract class FilePlugin implements SimplexPlugin {
 			long capacity = getCapacity(dir.getPath());
 			if(capacity < MIN_CONNECTION_LENGTH) return null;
 			OutputStream out = new FileOutputStream(f);
-			return new FileTransportWriter(f, out, capacity, this);
+			return new FileTransportWriter(f, out, capacity, maxLatency, this);
 		} catch(IOException e) {
 			if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			f.delete();
diff --git a/briar-core/src/net/sf/briar/plugins/file/FileTransportWriter.java b/briar-core/src/net/sf/briar/plugins/file/FileTransportWriter.java
index c40e0ca33fe5566e61239754d3e8a4a7afd5609d..e86b2f81457900db3eadf2be7793cc5467ac7dee 100644
--- a/briar-core/src/net/sf/briar/plugins/file/FileTransportWriter.java
+++ b/briar-core/src/net/sf/briar/plugins/file/FileTransportWriter.java
@@ -16,14 +16,15 @@ class FileTransportWriter implements SimplexTransportWriter {
 
 	private final File file;
 	private final OutputStream out;
-	private final long capacity;
+	private final long capacity, maxLatency;
 	private final FilePlugin plugin;
 
 	FileTransportWriter(File file, OutputStream out, long capacity,
-			FilePlugin plugin) {
+			long maxLatency, FilePlugin plugin) {
 		this.file = file;
 		this.out = out;
 		this.capacity = capacity;
+		this.maxLatency = maxLatency;
 		this.plugin = plugin;
 	}
 
@@ -31,6 +32,10 @@ class FileTransportWriter implements SimplexTransportWriter {
 		return capacity;
 	}
 
+	public long getMaxLatency() {
+		return maxLatency;
+	}
+
 	public OutputStream getOutputStream() {
 		return out;
 	}
diff --git a/briar-core/src/net/sf/briar/plugins/file/RemovableDrivePlugin.java b/briar-core/src/net/sf/briar/plugins/file/RemovableDrivePlugin.java
index c0d31920154b03be37dcad3246486af23850988d..fecb1dfe7157ccbe73529470931d32513cec92c6 100644
--- a/briar-core/src/net/sf/briar/plugins/file/RemovableDrivePlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/file/RemovableDrivePlugin.java
@@ -31,15 +31,13 @@ implements RemovableDriveMonitor.Callback {
 
 	private final RemovableDriveFinder finder;
 	private final RemovableDriveMonitor monitor;
-	private final long maxLatency;
 
 	RemovableDrivePlugin(@PluginExecutor Executor pluginExecutor,
 			SimplexPluginCallback callback, RemovableDriveFinder finder,
 			RemovableDriveMonitor monitor, long maxLatency) {
-		super(pluginExecutor, callback);
+		super(pluginExecutor, callback, maxLatency);
 		this.finder = finder;
 		this.monitor = monitor;
-		this.maxLatency = maxLatency;
 	}
 
 	public TransportId getId() {
diff --git a/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java b/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java
index 5e459b6ddec09f34b42a5f4642cf0a31c26af82a..cb838c5988a4cce7438570bbf30f01af25a6008f 100644
--- a/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java
@@ -234,6 +234,10 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 
 		private final CountDownLatch finished = new CountDownLatch(1);
 
+		public long getMaxLatency() {
+			return maxLatency;
+		}
+
 		public InputStream getInputStream() throws IOException {
 			return modem.getInputStream();
 		}
diff --git a/briar-core/src/net/sf/briar/plugins/tcp/LanTcpPlugin.java b/briar-core/src/net/sf/briar/plugins/tcp/LanTcpPlugin.java
index 2b00190a9c7c9346b8c31442c8b02de4b0f35d7d..8b82d7e75038c5371ceec577e46d67bc02d83027 100644
--- a/briar-core/src/net/sf/briar/plugins/tcp/LanTcpPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/tcp/LanTcpPlugin.java
@@ -152,7 +152,7 @@ class LanTcpPlugin extends TcpPlugin {
 							// Connect back on the advertised TCP port
 							Socket s = new Socket(packet.getAddress(), port);
 							s.setSoTimeout(0);
-							return new TcpTransportConnection(s);
+							return new TcpTransportConnection(s, maxLatency);
 						} catch(IOException e) {
 							if(LOG.isLoggable(WARNING))
 								LOG.log(WARNING, e.toString(), e);
@@ -290,7 +290,7 @@ class LanTcpPlugin extends TcpPlugin {
 					ss.setSoTimeout(wait < 1 ? 1 : wait);
 					Socket s = ss.accept();
 					s.setSoTimeout(0);
-					return new TcpTransportConnection(s);
+					return new TcpTransportConnection(s, maxLatency);
 				} catch(SocketTimeoutException e) {
 					now = clock.currentTimeMillis();
 					if(now < end) {
diff --git a/briar-core/src/net/sf/briar/plugins/tcp/TcpPlugin.java b/briar-core/src/net/sf/briar/plugins/tcp/TcpPlugin.java
index 5af3fd8b06ef35d078f52f28fe850967ba2e4057..bd129a6cc01c23480a16c8f5fdd8ad2479e545df 100644
--- a/briar-core/src/net/sf/briar/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/tcp/TcpPlugin.java
@@ -131,8 +131,8 @@ abstract class TcpPlugin implements DuplexPlugin {
 				tryToClose(ss);
 				return;
 			}
-			TcpTransportConnection conn = new TcpTransportConnection(s);
-			callback.incomingConnectionCreated(conn);
+			callback.incomingConnectionCreated(new TcpTransportConnection(s,
+					maxLatency));
 			if(!running) return;
 		}
 	}
@@ -177,7 +177,7 @@ abstract class TcpPlugin implements DuplexPlugin {
 		try {
 			s.setSoTimeout(0);
 			s.connect(addr);
-			return new TcpTransportConnection(s);
+			return new TcpTransportConnection(s, maxLatency);
 		} catch(IOException e) {
 			if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
 			return null;
diff --git a/briar-core/src/net/sf/briar/plugins/tcp/TcpTransportConnection.java b/briar-core/src/net/sf/briar/plugins/tcp/TcpTransportConnection.java
index 379b706da63964cf809c213ccb22fb6cb01b0227..ee27cf22a6750de813057dcea3b954d1e853301d 100644
--- a/briar-core/src/net/sf/briar/plugins/tcp/TcpTransportConnection.java
+++ b/briar-core/src/net/sf/briar/plugins/tcp/TcpTransportConnection.java
@@ -10,9 +10,15 @@ import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
 class TcpTransportConnection implements DuplexTransportConnection {
 
 	private final Socket socket;
+	private final long maxLatency;
 
-	TcpTransportConnection(Socket socket) {
+	TcpTransportConnection(Socket socket, long maxLatency) {
 		this.socket = socket;
+		this.maxLatency = maxLatency;
+	}
+
+	public long getMaxLatency() {
+		return maxLatency;
 	}
 
 	public InputStream getInputStream() throws IOException {
diff --git a/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java b/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java
index 2b1fea717dec3023e48fe0999dcdccab6c7d0d65..860963cf99cf84268d66253093f39290e0b323ac 100644
--- a/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/tor/TorPlugin.java
@@ -195,8 +195,8 @@ class TorPlugin implements DuplexPlugin {
 				tryToClose(ss);
 				return;
 			}
-			TorTransportConnection conn = new TorTransportConnection(s);
-			callback.incomingConnectionCreated(conn);
+			callback.incomingConnectionCreated(new TorTransportConnection(s,
+					maxLatency));
 			synchronized(this) {
 				if(!running) return;
 			}
@@ -277,7 +277,7 @@ class TorPlugin implements DuplexPlugin {
 			if(LOG.isLoggable(INFO)) LOG.info("Connecting to hidden service");
 			NetSocket s = nl.createNetSocket(null, null, addr);
 			if(LOG.isLoggable(INFO)) LOG.info("Connected to hidden service");
-			return new TorTransportConnection(s);
+			return new TorTransportConnection(s, maxLatency);
 		} catch(IOException e) {
 			if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
 			return null;
diff --git a/briar-core/src/net/sf/briar/plugins/tor/TorTransportConnection.java b/briar-core/src/net/sf/briar/plugins/tor/TorTransportConnection.java
index c258e0dbe7f5e8c76519ee5b10a3c01e6a085691..de785e687a1bac234d0ff7646be709e598908eca 100644
--- a/briar-core/src/net/sf/briar/plugins/tor/TorTransportConnection.java
+++ b/briar-core/src/net/sf/briar/plugins/tor/TorTransportConnection.java
@@ -11,9 +11,15 @@ import org.silvertunnel.netlib.api.NetSocket;
 class TorTransportConnection implements DuplexTransportConnection {
 
 	private final NetSocket socket;
+	private final long maxLatency;
 
-	TorTransportConnection(NetSocket socket) {
+	TorTransportConnection(NetSocket socket, long maxLatency) {
 		this.socket = socket;
+		this.maxLatency = maxLatency;
+	}
+
+	public long getMaxLatency() {
+		return maxLatency;
 	}
 
 	public InputStream getInputStream() throws IOException {
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 6099975be85d806a061b9ab5d5725f11b0cfa5ad..a952cb341a04e06ee88a7867ef3301968126b684 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -503,12 +503,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		} catch(NoSuchContactException expected) {}
 
 		try {
-			db.generateBatch(contactId, 123);
+			db.generateBatch(contactId, 123, 456);
 			fail();
 		} catch(NoSuchContactException expected) {}
 
 		try {
-			db.generateBatch(contactId, 123, Arrays.asList(messageId));
+			db.generateBatch(contactId, 123, 456, Arrays.asList(messageId));
 			fail();
 		} catch(NoSuchContactException expected) {}
 
@@ -696,14 +696,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getRawMessage(txn, messageId1);
 			will(returnValue(raw1));
 			// Record the outstanding messages
-			// FIXME: Calculate the expiry time
 			oneOf(database).addOutstandingMessages(txn, contactId, sendable,
 					Long.MAX_VALUE);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);
 
-		assertEquals(messages, db.generateBatch(contactId, size * 2));
+		assertEquals(messages, db.generateBatch(contactId, size * 2,
+				Long.MAX_VALUE));
 
 		context.assertIsSatisfied();
 	}
@@ -733,16 +733,15 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(raw1)); // Message is sendable
 			oneOf(database).getRawMessageIfSendable(txn, contactId, messageId2);
 			will(returnValue(null)); // Message is not sendable
-			// Record the outstanding messages
-			// FIXME: Calculate the expiry time
+			// Record the outstanding message
 			oneOf(database).addOutstandingMessages(txn, contactId,
-					Collections.singletonList(messageId1), Long.MAX_VALUE);
+					Arrays.asList(messageId1), Long.MAX_VALUE);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);
 
 		assertEquals(messages, db.generateBatch(contactId, size * 3,
-				requested));
+				Long.MAX_VALUE, requested));
 
 		context.assertIsSatisfied();
 	}
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index 17546db32a11b47a94684ccbf7a4320b6fccae50..cbe723448c18f530668b0802277724d3c334b817 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -524,9 +524,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertTrue(it.hasNext());
 		assertEquals(messageId, it.next());
 		assertFalse(it.hasNext());
-		// FIXME: Calculate the expiry time
-		db.addOutstandingMessages(txn, contactId,
-				Collections.singletonList(messageId), Long.MAX_VALUE);
+		db.addOutstandingMessages(txn, contactId, Arrays.asList(messageId),
+				Long.MAX_VALUE);
 
 		// The message should no longer be sendable
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnectionTest.java b/briar-tests/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnectionTest.java
index 6e6d261c7a64726dcb2f6ab8b2a1f6fac34ece99..8843642c004b2664dadee81779233a15fa353ea5 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnectionTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/OutgoingSimplexConnectionTest.java
@@ -87,7 +87,7 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 	public void testConnectionTooShort() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
-				out, MAX_PACKET_LENGTH, true);
+				out, MAX_PACKET_LENGTH, Long.MAX_VALUE, true);
 		ConnectionContext ctx = new ConnectionContext(contactId, transportId,
 				secret, 0, true);
 		OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
@@ -105,7 +105,7 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 	public void testNothingToSend() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
-				out, MIN_CONNECTION_LENGTH, true);
+				out, MIN_CONNECTION_LENGTH, Long.MAX_VALUE, true);
 		ConnectionContext ctx = new ConnectionContext(contactId, transportId,
 				secret, 0, true);
 		OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
@@ -134,7 +134,8 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 			oneOf(db).generateAck(with(contactId), with(any(int.class)));
 			will(returnValue(null));
 			// No messages to send
-			oneOf(db).generateBatch(with(contactId), with(any(int.class)));
+			oneOf(db).generateBatch(with(contactId), with(any(int.class)),
+					with(any(long.class)));
 			will(returnValue(null));
 		}});
 		connection.write();
@@ -150,7 +151,7 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 	public void testSomethingToSend() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
-				out, MIN_CONNECTION_LENGTH, true);
+				out, MIN_CONNECTION_LENGTH, Long.MAX_VALUE, true);
 		ConnectionContext ctx = new ConnectionContext(contactId, transportId,
 				secret, 0, true);
 		OutgoingSimplexConnection connection = new OutgoingSimplexConnection(db,
@@ -183,10 +184,12 @@ public class OutgoingSimplexConnectionTest extends BriarTestCase {
 			oneOf(db).generateAck(with(contactId), with(any(int.class)));
 			will(returnValue(null));
 			// One message to send
-			oneOf(db).generateBatch(with(contactId), with(any(int.class)));
+			oneOf(db).generateBatch(with(contactId), with(any(int.class)),
+					with(any(long.class)));
 			will(returnValue(Collections.singletonList(raw)));
 			// No more messages
-			oneOf(db).generateBatch(with(contactId), with(any(int.class)));
+			oneOf(db).generateBatch(with(contactId), with(any(int.class)),
+					with(any(long.class)));
 			will(returnValue(null));
 		}});
 		connection.write();
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
index 15affd913bf1464b28e2e9c60359aad9f636381c..c5f9a402c4c48e685a06f562ca9064aad104b17a 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -126,7 +126,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		PacketWriterFactory packetWriterFactory =
 				alice.getInstance(PacketWriterFactory.class);
 		TestSimplexTransportWriter transport = new TestSimplexTransportWriter(
-				out, Long.MAX_VALUE, false);
+				out, Long.MAX_VALUE, Long.MAX_VALUE, false);
 		ConnectionContext ctx = km.getConnectionContext(contactId, transportId);
 		assertNotNull(ctx);
 		OutgoingSimplexConnection simplex = new OutgoingSimplexConnection(db,
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/TestSimplexTransportWriter.java b/briar-tests/src/net/sf/briar/messaging/simplex/TestSimplexTransportWriter.java
index 539da8456e61ab6727e9c59f84ab769a92ffe01d..e2fc34ed269d01e185206bd23698112c63ba0448 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/TestSimplexTransportWriter.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/TestSimplexTransportWriter.java
@@ -8,15 +8,16 @@ import net.sf.briar.api.plugins.simplex.SimplexTransportWriter;
 class TestSimplexTransportWriter implements SimplexTransportWriter {
 
 	private final ByteArrayOutputStream out;
-	private final long capacity;
+	private final long capacity, maxLatency;
 	private final boolean flush;
 
 	private boolean disposed = false, exception = false;
 
 	TestSimplexTransportWriter(ByteArrayOutputStream out, long capacity,
-			boolean flush) {
+			long maxLatency, boolean flush) {
 		this.out = out;
 		this.capacity = capacity;
+		this.maxLatency = maxLatency;
 		this.flush = flush;
 	}
 
@@ -24,6 +25,10 @@ class TestSimplexTransportWriter implements SimplexTransportWriter {
 		return capacity;
 	}
 
+	public long getMaxLatency() {
+		return maxLatency;
+	}
+
 	public OutputStream getOutputStream() {
 		return out;
 	}