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 6399ad328a2b2ae755e22f09be95c781c07bde79..e986f4a5fadd8b472e7497c50d4230fe5518945c 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -11,8 +11,8 @@ import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
-import net.sf.briar.api.protocol.ExpiryAck;
-import net.sf.briar.api.protocol.ExpiryUpdate;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
@@ -99,22 +99,22 @@ public interface DatabaseComponent {
 			Collection<MessageId> requested) throws DbException;
 
 	/**
-	 * Generates an expiry ack for the given contact. Returns null if no ack
-	 * is due.
+	 * Generates an offer for the given contact. Returns null if there are no
+	 * messages to offer.
 	 */
-	ExpiryAck generateExpiryAck(ContactId c) throws DbException;
+	Offer generateOffer(ContactId c, int maxMessages) throws DbException;
 
 	/**
-	 * Generates an expiry update for the given contact. Returns null if no
-	 * update is due.
+	 * Generates a retention ack for the given contact. Returns null if no ack
+	 * is due.
 	 */
-	ExpiryUpdate generateExpiryUpdate(ContactId c) throws DbException;
+	RetentionAck generateRetentionAck(ContactId c) throws DbException;
 
 	/**
-	 * Generates an offer for the given contact. Returns null if there are no
-	 * messages to offer.
+	 * Generates a retention update for the given contact. Returns null if no
+	 * update is due.
 	 */
-	Offer generateOffer(ContactId c, int maxMessages) throws DbException;
+	RetentionUpdate generateRetentionUpdate(ContactId c) throws DbException;
 
 	/**
 	 * Generates a subscription ack for the given contact. Returns null if no
@@ -200,12 +200,6 @@ public interface DatabaseComponent {
 	/** Processes an ack from the given contact. */
 	void receiveAck(ContactId c, Ack a) throws DbException;
 
-	/** Processes an expiry ack from the given contact. */
-	void receiveExpiryAck(ContactId c, ExpiryAck a) throws DbException;
-
-	/** Processes an expiry update from the given contact. */
-	void receiveExpiryUpdate(ContactId c, ExpiryUpdate u) throws DbException;
-
 	/** Processes a message from the given contact. */
 	void receiveMessage(ContactId c, Message m) throws DbException;
 
@@ -219,6 +213,13 @@ public interface DatabaseComponent {
 	 */
 	Request receiveOffer(ContactId c, Offer o) throws DbException;
 
+	/** Processes a retention ack from the given contact. */
+	void receiveRetentionAck(ContactId c, RetentionAck a) throws DbException;
+
+	/** Processes a retention update from the given contact. */
+	void receiveRetentionUpdate(ContactId c, RetentionUpdate u)
+			throws DbException;
+
 	/** Processes a subscription ack from the given contact. */
 	void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
 			throws DbException;
diff --git a/briar-api/src/net/sf/briar/api/protocol/ExpiryUpdate.java b/briar-api/src/net/sf/briar/api/protocol/ExpiryUpdate.java
deleted file mode 100644
index 4b3c81f3080cfb9a7a43e2c085bd45c5a79e97fe..0000000000000000000000000000000000000000
--- a/briar-api/src/net/sf/briar/api/protocol/ExpiryUpdate.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package net.sf.briar.api.protocol;
-
-/**
- * A packet updating the recipient's view of the expiry time of the sender's
- * database.
- */
-public class ExpiryUpdate {
-
-	private final long expiry, version;
-
-	public ExpiryUpdate(long expiry, long version) {
-		this.expiry = expiry;
-		this.version = version;
-	}
-
-	public long getExpiryTime() {
-		return expiry;
-	}
-
-	public long getVersionNumber() {
-		return version;
-	}
-}
diff --git a/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java b/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java
index 0499180c03d0835fc001a873681ef11eca09ea79..12f675b5bdb3d1cf46aba7c30430e193f2a64619 100644
--- a/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java
+++ b/briar-api/src/net/sf/briar/api/protocol/ProtocolReader.java
@@ -9,12 +9,6 @@ public interface ProtocolReader {
 	boolean hasAck() throws IOException;
 	Ack readAck() throws IOException;
 
-	boolean hasExpiryAck() throws IOException;
-	ExpiryAck readExpiryAck() throws IOException;
-
-	boolean hasExpiryUpdate() throws IOException;
-	ExpiryUpdate readExpiryUpdate() throws IOException;
-
 	boolean hasMessage() throws IOException;
 	UnverifiedMessage readMessage() throws IOException;
 
@@ -24,6 +18,12 @@ public interface ProtocolReader {
 	boolean hasRequest() throws IOException;
 	Request readRequest() throws IOException;
 
+	boolean hasRetentionAck() throws IOException;
+	RetentionAck readRetentionAck() throws IOException;
+
+	boolean hasRetentionUpdate() throws IOException;
+	RetentionUpdate readRetentionUpdate() throws IOException;
+
 	boolean hasSubscriptionAck() throws IOException;
 	SubscriptionAck readSubscriptionAck() throws IOException;
 
diff --git a/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java b/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java
index 718e8be37cb94748b4d5807dd677fda7c7ce12d9..f2d9fa2b6dc7b3c8228bda415c9a8245a3eaf8b6 100644
--- a/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java
+++ b/briar-api/src/net/sf/briar/api/protocol/ProtocolWriter.java
@@ -10,16 +10,16 @@ public interface ProtocolWriter {
 
 	void writeAck(Ack a) throws IOException;
 
-	void writeExpiryAck(ExpiryAck a) throws IOException;
-
-	void writeExpiryUpdate(ExpiryUpdate e) throws IOException;
-
 	void writeMessage(byte[] raw) throws IOException;
 
 	void writeOffer(Offer o) throws IOException;
 
 	void writeRequest(Request r) throws IOException;
 
+	void writeRetentionAck(RetentionAck a) throws IOException;
+
+	void writeRetentionUpdate(RetentionUpdate u) throws IOException;
+
 	void writeSubscriptionAck(SubscriptionAck a) throws IOException;
 
 	void writeSubscriptionUpdate(SubscriptionUpdate u) throws IOException;
diff --git a/briar-api/src/net/sf/briar/api/protocol/ExpiryAck.java b/briar-api/src/net/sf/briar/api/protocol/RetentionAck.java
similarity index 56%
rename from briar-api/src/net/sf/briar/api/protocol/ExpiryAck.java
rename to briar-api/src/net/sf/briar/api/protocol/RetentionAck.java
index 08640279d24aa27145ece490e684a9fa9a43812f..f5e46caae96fd6d16afd873a9a956452d438c017 100644
--- a/briar-api/src/net/sf/briar/api/protocol/ExpiryAck.java
+++ b/briar-api/src/net/sf/briar/api/protocol/RetentionAck.java
@@ -1,11 +1,11 @@
 package net.sf.briar.api.protocol;
 
-/** A packet acknowledging a (@link ExpiryUpdate} */
-public class ExpiryAck {
+/** A packet acknowledging a (@link RetentionUpdate} */
+public class RetentionAck {
 
 	private final long version;
 
-	public ExpiryAck(long version) {
+	public RetentionAck(long version) {
 		this.version = version;
 	}
 
diff --git a/briar-api/src/net/sf/briar/api/protocol/RetentionUpdate.java b/briar-api/src/net/sf/briar/api/protocol/RetentionUpdate.java
new file mode 100644
index 0000000000000000000000000000000000000000..837154cd31153a374a123d5297ea912b1c9f2d92
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/protocol/RetentionUpdate.java
@@ -0,0 +1,23 @@
+package net.sf.briar.api.protocol;
+
+/**
+ * A packet updating the recipient's view of the retention time of the sender's
+ * database.
+ */
+public class RetentionUpdate {
+
+	private final long retention, version;
+
+	public RetentionUpdate(long retention, long version) {
+		this.retention = retention;
+		this.version = version;
+	}
+
+	public long getRetentionTime() {
+		return retention;
+	}
+
+	public long getVersionNumber() {
+		return version;
+	}
+}
diff --git a/briar-api/src/net/sf/briar/api/protocol/Types.java b/briar-api/src/net/sf/briar/api/protocol/Types.java
index d17e26ab5f50799feea3cda98586c9c482cab70e..eefa685cf6b6bd07132696d81530049c95f42275 100644
--- a/briar-api/src/net/sf/briar/api/protocol/Types.java
+++ b/briar-api/src/net/sf/briar/api/protocol/Types.java
@@ -6,11 +6,11 @@ public interface Types {
 	int AUTHOR = 0;
 	int GROUP = 1;
 	int ACK = 2;
-	int EXPIRY_ACK = 3;
-	int EXPIRY_UPDATE = 4;
-	int MESSAGE = 5;
-	int OFFER = 6;
-	int REQUEST = 7;
+	int MESSAGE = 3;
+	int OFFER = 4;
+	int REQUEST = 5;
+	int RETENTION_ACK = 6;
+	int RETENTION_UPDATE = 7;
 	int SUBSCRIPTION_ACK = 8;
 	int SUBSCRIPTION_UPDATE = 9;
 	int TRANSPORT_ACK = 10;
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index 3e1e06ec2276c0a31e67f488cb26fc25dace6713..1a65cccc5fd27d724dc44a6b2e5ea3e17e588a43 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -11,8 +11,8 @@ import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.MessageHeader;
 import net.sf.briar.api.protocol.AuthorId;
-import net.sf.briar.api.protocol.ExpiryAck;
-import net.sf.briar.api.protocol.ExpiryUpdate;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
@@ -36,9 +36,9 @@ import net.sf.briar.api.transport.TemporarySecret;
  * deadlock, locks must be acquired in the following (alphabetical) order:
  * <ul>
  * <li> contact
- * <li> expiry
  * <li> message
  * <li> rating
+ * <li> retention
  * <li> subscription
  * <li> transport
  * <li> window
@@ -208,21 +208,6 @@ interface Database<T> {
 	 */
 	Collection<ContactTransport> getContactTransports(T txn) throws DbException;
 
-	/**
-	 * Returns an expiry ack for the given contact, or null if no ack is due.
-	 * <p>
-	 * Locking: contact read, expiry write.
-	 */
-	ExpiryAck getExpiryAck(T txn, ContactId c) throws DbException;
-
-	/**
-	 * Returns an expiry update for the given contact, or null if no update is
-	 * due.
-	 * <p>
-	 * Locking: contact read, expiry write.
-	 */
-	ExpiryUpdate getExpiryUpdate(T txn, ContactId c) throws DbException;
-
 	/**
 	 * Returns the amount of free storage space available to the database, in
 	 * bytes. This is based on the minimum of the space available on the device
@@ -346,6 +331,21 @@ interface Database<T> {
 	Map<ContactId, TransportProperties> getRemoteProperties(T txn,
 			TransportId t) throws DbException;
 
+	/**
+	 * Returns a retention ack for the given contact, or null if no ack is due.
+	 * <p>
+	 * Locking: contact read, retention write.
+	 */
+	RetentionAck getRetentionAck(T txn, ContactId c) throws DbException;
+
+	/**
+	 * Returns a retention update for the given contact, or null if no update
+	 * is due.
+	 * <p>
+	 * Locking: contact read, retention write.
+	 */
+	RetentionUpdate getRetentionUpdate(T txn, ContactId c) throws DbException;
+
 	/**
 	 * Returns all temporary secrets.
 	 * <p>
@@ -459,12 +459,12 @@ interface Database<T> {
 			long period) throws DbException;
 
 	/**
-	 * Increments the expiry versions for all contacts to indicate that the
-	 * database's expiry time has changed and expiry updates should be sent.
+	 * Increments the retention time versions for all contacts to indicate that
+	 * the database's retention time has changed and updates should be sent.
 	 * <p>
-	 * Locking: contact read, expiry write.
+	 * Locking: contact read, retention write.
 	 */
-	void incrementExpiryVersions(T txn) throws DbException;
+	void incrementRetentionVersions(T txn) throws DbException;
 
 	/**
 	 * Merges the given configuration with the existing configuration for the
@@ -549,16 +549,6 @@ interface Database<T> {
 	void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException;
 
-	/**
-	 * Sets the expiry time of the given contact's database, unless an update
-	 * with an equal or higher version number has already been received from
-	 * the contact.
-	 * <p>
-	 * Locking: contact read, expiry write.
-	 */
-	void setExpiryTime(T txn, ContactId c, long expiry, long version)
-			throws DbException;
-
 	/**
 	 * Sets the user's rating for the given author.
 	 * <p>
@@ -585,6 +575,16 @@ interface Database<T> {
 	void setRemoteProperties(T txn, ContactId c, TransportUpdate u)
 			throws DbException;
 
+	/**
+	 * Sets the retention time of the given contact's database, unless an
+	 * update with an equal or higher version number has already been received
+	 * from the contact.
+	 * <p>
+	 * Locking: contact read, retention write.
+	 */
+	void setRetentionTime(T txn, ContactId c, long retention, long version)
+			throws DbException;
+
 	/**
 	 * Sets the sendability score of the given message.
 	 * <p>
@@ -630,12 +630,12 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Records an expiry ack from the given contact for the given version
+	 * Records a retention ack from the given contact for the given version
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: contact read, expiry write.
+	 * Locking: contact read, retention write.
 	 */
-	void setExpiryUpdateAcked(T txn, ContactId c, long version)
+	void setRetentionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
 
 	/**
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 40ccb3af1a318d7681d21ad8cc4f635ea0aafe22..515bbf06027b380a28451118ae6d59f8fb00fb83 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -46,8 +46,8 @@ import net.sf.briar.api.db.event.TransportRemovedEvent;
 import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
-import net.sf.briar.api.protocol.ExpiryAck;
-import net.sf.briar.api.protocol.ExpiryUpdate;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
@@ -83,12 +83,12 @@ DatabaseCleaner.Callback {
 
 	private final ReentrantReadWriteLock contactLock =
 			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock expiryLock =
-			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock messageLock =
 			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock ratingLock =
 			new ReentrantReadWriteLock(true);
+	private final ReentrantReadWriteLock retentionLock =
+			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock subscriptionLock =
 			new ReentrantReadWriteLock(true);
 	private final ReentrantReadWriteLock transportLock =
@@ -594,78 +594,79 @@ DatabaseCleaner.Callback {
 		return Collections.unmodifiableList(messages);
 	}
 
-	public ExpiryAck generateExpiryAck(ContactId c) throws DbException {
+	public Offer generateOffer(ContactId c, int maxMessages)
+			throws DbException {
+		Collection<MessageId> offered;
 		contactLock.readLock().lock();
 		try {
-			expiryLock.writeLock().lock();
+			messageLock.readLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
 					if(!db.containsContact(txn, c))
 						throw new NoSuchContactException();
-					ExpiryAck a = db.getExpiryAck(txn, c);
+					offered = db.getMessagesToOffer(txn, c, maxMessages);
 					db.commitTransaction(txn);
-					return a;
 				} catch(DbException e) {
 					db.abortTransaction(txn);
 					throw e;
 				}
 			} finally {
-				expiryLock.writeLock().unlock();
+				messageLock.readLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
 		}
+		return new Offer(offered);
 	}
 
-	public ExpiryUpdate generateExpiryUpdate(ContactId c) throws DbException {
+	public RetentionAck generateRetentionAck(ContactId c) throws DbException {
 		contactLock.readLock().lock();
 		try {
-			expiryLock.writeLock().lock();
+			retentionLock.writeLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
 					if(!db.containsContact(txn, c))
 						throw new NoSuchContactException();
-					ExpiryUpdate e = db.getExpiryUpdate(txn, c);
+					RetentionAck a = db.getRetentionAck(txn, c);
 					db.commitTransaction(txn);
-					return e;
+					return a;
 				} catch(DbException e) {
 					db.abortTransaction(txn);
 					throw e;
 				}
 			} finally {
-				expiryLock.writeLock().unlock();
+				retentionLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
 		}
 	}
 
-	public Offer generateOffer(ContactId c, int maxMessages)
+	public RetentionUpdate generateRetentionUpdate(ContactId c)
 			throws DbException {
-		Collection<MessageId> offered;
 		contactLock.readLock().lock();
 		try {
-			messageLock.readLock().lock();
+			retentionLock.writeLock().lock();
 			try {
 				T txn = db.startTransaction();
 				try {
 					if(!db.containsContact(txn, c))
 						throw new NoSuchContactException();
-					offered = db.getMessagesToOffer(txn, c, maxMessages);
+					RetentionUpdate u = db.getRetentionUpdate(txn, c);
 					db.commitTransaction(txn);
+					return u;
 				} catch(DbException e) {
 					db.abortTransaction(txn);
 					throw e;
 				}
 			} finally {
-				messageLock.readLock().unlock();
+				retentionLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		return new Offer(offered);
 	}
 
 	public SubscriptionAck generateSubscriptionAck(ContactId c)
@@ -1090,54 +1091,6 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public void receiveExpiryAck(ContactId c, ExpiryAck a) throws DbException {
-		contactLock.readLock().lock();
-		try {
-			expiryLock.writeLock().lock();
-			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setExpiryUpdateAcked(txn, c, a.getVersionNumber());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				expiryLock.writeLock().unlock();
-			}
-		} finally {
-			contactLock.readLock().unlock();
-		}
-	}
-
-	public void receiveExpiryUpdate(ContactId c, ExpiryUpdate u)
-			throws DbException {
-		contactLock.readLock().lock();
-		try {
-			expiryLock.writeLock().lock();
-			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setExpiryTime(txn, c, u.getExpiryTime(),
-							u.getVersionNumber());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				expiryLock.writeLock().unlock();
-			}
-		} finally {
-			contactLock.readLock().unlock();
-		}
-	}
-
 	public void receiveMessage(ContactId c, Message m) throws DbException {
 		boolean added = false;
 		contactLock.readLock().lock();
@@ -1226,6 +1179,55 @@ DatabaseCleaner.Callback {
 		return new Request(request, offered.size());
 	}
 
+	public void receiveRetentionAck(ContactId c, RetentionAck a)
+			throws DbException {
+		contactLock.readLock().lock();
+		try {
+			retentionLock.writeLock().lock();
+			try {
+				T txn = db.startTransaction();
+				try {
+					if(!db.containsContact(txn, c))
+						throw new NoSuchContactException();
+					db.setRetentionUpdateAcked(txn, c, a.getVersionNumber());
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				retentionLock.writeLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+	}
+
+	public void receiveRetentionUpdate(ContactId c, RetentionUpdate u)
+			throws DbException {
+		contactLock.readLock().lock();
+		try {
+			retentionLock.writeLock().lock();
+			try {
+				T txn = db.startTransaction();
+				try {
+					if(!db.containsContact(txn, c))
+						throw new NoSuchContactException();
+					db.setRetentionTime(txn, c, u.getRetentionTime(),
+							u.getVersionNumber());
+					db.commitTransaction(txn);
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				retentionLock.writeLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+	}
+
 	public void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
 			throws DbException {
 		contactLock.readLock().lock();
@@ -1606,9 +1608,9 @@ DatabaseCleaner.Callback {
 		boolean removed = false;
 		contactLock.readLock().lock();
 		try {
-			expiryLock.writeLock().lock();
+			messageLock.writeLock().lock();
 			try {
-				messageLock.writeLock().lock();
+				retentionLock.writeLock().lock();
 				try {
 					T txn = db.startTransaction();
 					try {
@@ -1616,7 +1618,7 @@ DatabaseCleaner.Callback {
 								db.getOldMessages(txn, size);
 						if(!old.isEmpty()) {
 							for(MessageId m : old) removeMessage(txn, m);
-							db.incrementExpiryVersions(txn);
+							db.incrementRetentionVersions(txn);
 							removed = true;
 						}
 						db.commitTransaction(txn);
@@ -1625,10 +1627,10 @@ DatabaseCleaner.Callback {
 						throw e;
 					}
 				} finally {
-					messageLock.writeLock().unlock();
+					retentionLock.writeLock().unlock();
 				}
 			} finally {
-				expiryLock.writeLock().unlock();
+				messageLock.writeLock().unlock();
 			}
 		} finally {
 			contactLock.readLock().unlock();
diff --git a/briar-core/src/net/sf/briar/db/DatabaseConstants.java b/briar-core/src/net/sf/briar/db/DatabaseConstants.java
index eb00f1290c2c7e5500c98b62766520cbb31690e1..16686d41c21a0939fee1350d71d4b1df92a50400 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseConstants.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseConstants.java
@@ -40,7 +40,7 @@ interface DatabaseConstants {
 	 * The timestamp of the oldest message in the database is rounded using
 	 * this modulus to avoid revealing the presence of any particular message.
 	 */
-	long EXPIRY_MODULUS = 60L * 60L * 1000L; // 1 hour
+	long RETENTION_MODULUS = 60L * 60L * 1000L; // 1 hour
 
 	/**
 	 * The time in milliseconds after which a subscription or transport update
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index d96a0ae6a44778bde8b1fc4125696c7ce47a4b3e..dbfcb80a56ae38a4f0c686f72aeca2841ea09dfa 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -3,7 +3,7 @@ package net.sf.briar.db;
 import static java.sql.Types.BINARY;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
-import static net.sf.briar.db.DatabaseConstants.EXPIRY_MODULUS;
+import static net.sf.briar.db.DatabaseConstants.RETENTION_MODULUS;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -31,8 +31,8 @@ import net.sf.briar.api.db.DbClosedException;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.MessageHeader;
 import net.sf.briar.api.protocol.AuthorId;
-import net.sf.briar.api.protocol.ExpiryAck;
-import net.sf.briar.api.protocol.ExpiryUpdate;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
@@ -58,20 +58,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " (contactId COUNTER,"
 					+ " PRIMARY KEY (contactId))";
 
-	// Locking: expiry
-	private static final String CREATE_EXPIRY_VERSIONS =
-			"CREATE TABLE expiryVersions"
-					+ " (contactId INT NOT NULL,"
-					+ " expiry BIGINT NOT NULL,"
-					+ " localVersion BIGINT NOT NULL,"
-					+ " localAcked BIGINT NOT NULL,"
-					+ " remoteVersion BIGINT NOT NULL,"
-					+ " remoteAcked BOOLEAN NOT NULL,"
-					+ " PRIMARY KEY (contactId),"
-					+ " FOREIGN KEY (contactId)"
-					+ " REFERENCES contacts (contactId)"
-					+ " ON DELETE CASCADE)";
-
 	// Locking: message
 	private static final String CREATE_MESSAGES =
 			"CREATE TABLE messages"
@@ -155,6 +141,20 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " rating SMALLINT NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
+	// Locking: contact read, retention
+	private static final String CREATE_RETENTION_VERSIONS =
+			"CREATE TABLE retentionVersions"
+					+ " (contactId INT NOT NULL,"
+					+ " retention BIGINT NOT NULL,"
+					+ " localVersion BIGINT NOT NULL,"
+					+ " localAcked BIGINT NOT NULL,"
+					+ " remoteVersion BIGINT NOT NULL,"
+					+ " remoteAcked BOOLEAN NOT NULL,"
+					+ " PRIMARY KEY (contactId),"
+					+ " FOREIGN KEY (contactId)"
+					+ " REFERENCES contacts (contactId)"
+					+ " ON DELETE CASCADE)";
+
 	// Locking: subscription
 	private static final String CREATE_GROUPS =
 			"CREATE TABLE groups"
@@ -355,7 +355,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		try {
 			s = txn.createStatement();
 			s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
-			s.executeUpdate(insertTypeNames(CREATE_EXPIRY_VERSIONS));
 			s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
 			s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
 			s.executeUpdate(INDEX_MESSAGES_BY_AUTHOR);
@@ -367,6 +366,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
 			s.executeUpdate(insertTypeNames(CREATE_FLAGS));
 			s.executeUpdate(insertTypeNames(CREATE_RATINGS));
+			s.executeUpdate(insertTypeNames(CREATE_RETENTION_VERSIONS));
 			s.executeUpdate(insertTypeNames(CREATE_GROUPS));
 			s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
 			s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS));
@@ -513,8 +513,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			// Create an expiry version row
-			sql = "INSERT INTO expiryVersions (contactId, expiry,"
+			// Create a retention version row
+			sql = "INSERT INTO retentionVersions (contactId, retention,"
 					+ " localVersion, localAcked, remoteVersion, remoteAcked)"
 					+ " VALUES (?, ZERO(), ?, ZERO(), ZERO(), TRUE)";
 			ps = txn.prepareStatement(sql);
@@ -1042,72 +1042,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		} else return f.length();
 	}
 
-	public ExpiryAck getExpiryAck(Connection txn, ContactId c)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT remoteVersion FROM expiryVersions"
-					+ " WHERE contactId = ? AND remoteAcked = FALSE";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			rs = ps.executeQuery();
-			if(!rs.next()) {
-				rs.close();
-				ps.close();
-				return null;
-			}
-			long version = rs.getLong(1);
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			sql = "UPDATE expiryVersions SET remoteAcked = TRUE"
-					+ " WHERE contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			int affected = ps.executeUpdate();
-			if(affected != 1) throw new DbStateException();
-			ps.close();
-			return new ExpiryAck(version);
-		} catch(SQLException e) {
-			tryToClose(ps);
-			tryToClose(rs);
-			throw new DbException(e);
-		}
-	}
-
-	public ExpiryUpdate getExpiryUpdate(Connection txn, ContactId c)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT timestamp, localVersion"
-					+ " FROM messages JOIN expiryVersions"
-					+ " WHERE contactId = ? AND localVersion > localAcked"
-					+ " ORDER BY timestamp LIMIT ?";
-			ps = txn.prepareStatement(sql);
-			ps.setInt(1, c.getInt());
-			ps.setInt(2, 1);
-			rs = ps.executeQuery();
-			if(!rs.next()) {
-				rs.close();
-				ps.close();
-				return null;
-			}
-			long expiry = rs.getLong(1);
-			expiry -= expiry % EXPIRY_MODULUS;
-			long version = rs.getLong(2);
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			return new ExpiryUpdate(expiry, version);
-		} catch(SQLException e) {
-			tryToClose(ps);
-			tryToClose(rs);
-			throw new DbException(e);
-		}
-	}
-
 	public MessageId getGroupMessageParent(Connection txn, MessageId m)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -1277,14 +1211,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
 					+ " AND cg.contactId = gv.contactId"
-					+ " JOIN expiryVersions AS ev"
-					+ " ON cg.contactId = ev.contactId"
+					+ " JOIN retentionVersions AS rv"
+					+ " ON cg.contactId = rv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE m.messageId = ?"
 					+ " AND cg.contactId = ?"
-					+ " AND timestamp >= expiry"
+					+ " AND timestamp >= retention"
 					+ " AND status = ?"
 					+ " AND sendability > ZERO()";
 			ps = txn.prepareStatement(sql);
@@ -1382,13 +1316,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
 					+ " AND cg.contactId = gv.contactId"
-					+ " JOIN expiryVersions AS ev"
-					+ " ON cg.contactId = ev.contactId"
+					+ " JOIN retentionVersions AS rv"
+					+ " ON cg.contactId = rv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE cg.contactId = ?"
-					+ " AND timestamp >= expiry"
+					+ " AND timestamp >= retention"
 					+ " AND status = ?"
 					+ " AND sendability > ZERO()"
 					+ " ORDER BY timestamp DESC LIMIT ?";
@@ -1548,6 +1482,72 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public RetentionAck getRetentionAck(Connection txn, ContactId c)
+			throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT remoteVersion FROM retentionVersions"
+					+ " WHERE contactId = ? AND remoteAcked = FALSE";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			rs = ps.executeQuery();
+			if(!rs.next()) {
+				rs.close();
+				ps.close();
+				return null;
+			}
+			long version = rs.getLong(1);
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			sql = "UPDATE retentionVersions SET remoteAcked = TRUE"
+					+ " WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			int affected = ps.executeUpdate();
+			if(affected != 1) throw new DbStateException();
+			ps.close();
+			return new RetentionAck(version);
+		} catch(SQLException e) {
+			tryToClose(ps);
+			tryToClose(rs);
+			throw new DbException(e);
+		}
+	}
+
+	public RetentionUpdate getRetentionUpdate(Connection txn, ContactId c)
+			throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT timestamp, localVersion"
+					+ " FROM messages JOIN retentionVersions"
+					+ " WHERE contactId = ? AND localVersion > localAcked"
+					+ " ORDER BY timestamp LIMIT ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setInt(2, 1);
+			rs = ps.executeQuery();
+			if(!rs.next()) {
+				rs.close();
+				ps.close();
+				return null;
+			}
+			long retention = rs.getLong(1);
+			retention -= retention % RETENTION_MODULUS;
+			long version = rs.getLong(2);
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			return new RetentionUpdate(retention, version);
+		} catch(SQLException e) {
+			tryToClose(ps);
+			tryToClose(rs);
+			throw new DbException(e);
+		}
+	}
+
 	public Collection<TemporarySecret> getSecrets(Connection txn)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -1642,13 +1642,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
 					+ " AND cg.contactId = gv.contactId"
-					+ " JOIN expiryVersions AS ev"
-					+ " ON cg.contactId = ev.contactId"
+					+ " JOIN retentionVersions AS rv"
+					+ " ON cg.contactId = rv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE cg.contactId = ?"
-					+ " AND timestamp >= expiry"
+					+ " AND timestamp >= retention"
 					+ " AND status = ?"
 					+ " AND sendability > ZERO()"
 					+ " ORDER BY timestamp DESC";
@@ -1974,13 +1974,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
 					+ " AND cg.contactId = gv.contactId"
-					+ " JOIN expiryVersions AS ev"
-					+ " ON cg.contactId = ev.contactId"
+					+ " JOIN retentionVersios AS rv"
+					+ " ON cg.contactId = rv.contactId"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE cg.contactId = ?"
-					+ " AND timestamp >= expiry"
+					+ " AND timestamp >= retention"
 					+ " AND status = ?"
 					+ " AND sendability > ZERO()"
 					+ " LIMIT ?";
@@ -2042,10 +2042,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void incrementExpiryVersions(Connection txn) throws DbException {
+	public void incrementRetentionVersions(Connection txn) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "UPDATE expiryVersions"
+			String sql = "UPDATE retentionVersions"
 					+ " SET localVersion = localVersion + ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, 1);
@@ -2323,15 +2323,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setExpiryTime(Connection txn, ContactId c, long expiry,
+	public void setRetentionTime(Connection txn, ContactId c, long retention,
 			long version) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "UPDATE expiryVersions"
-					+ " SET expiry = ?, remoteVersion = ?, remoteAcked = FALSE"
+			String sql = "UPDATE retentionVersions SET retention = ?,"
+					+ " remoteVersion = ?, remoteAcked = FALSE"
 					+ " WHERE contactId = ? AND remoteVersion < ?";
 			ps = txn.prepareStatement(sql);
-			ps.setLong(1, expiry);
+			ps.setLong(1, retention);
 			ps.setLong(2, version);
 			ps.setInt(3, c.getInt());
 			ps.setLong(4, version);
@@ -2344,11 +2344,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setExpiryUpdateAcked(Connection txn, ContactId c, long version)
-			throws DbException {
+	public void setRetentionUpdateAcked(Connection txn, ContactId c,
+			long version) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "UPDATE expiryVersions SET localAcked = ?"
+			String sql = "UPDATE retentionVersions SET localAcked = ?"
 					+ " WHERE contactId = ?"
 					+ " AND localAcked < ? AND localVersion >= ?";
 			ps = txn.prepareStatement(sql);
@@ -2672,11 +2672,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN groupVisibilities AS gv"
 					+ " ON m.groupId = gv.groupId"
 					+ " AND cg.contactId = gv.contactId"
-					+ " JOIN expiryVersions AS ev"
-					+ " ON cg.contactId = ev.contactId"
+					+ " JOIN retentionVersions AS rv"
+					+ " ON cg.contactId = rv.contactId"
 					+ " WHERE messageId = ?"
 					+ " AND cg.contactId = ?"
-					+ " AND timestamp >= expiry";
+					+ " AND timestamp >= retention";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			ps.setInt(2, c.getInt());
diff --git a/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java b/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java
index 1f4b5acdaa99f5367df35f50e498600b3a39a9a2..2e49cb2b6bad638f5e9c187d29a77defe5d147fa 100644
--- a/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java
+++ b/briar-core/src/net/sf/briar/protocol/ProtocolReaderImpl.java
@@ -4,8 +4,8 @@ import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTIES_PER_TRANSPORT;
 import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PROPERTY_LENGTH;
 import static net.sf.briar.api.protocol.Types.ACK;
-import static net.sf.briar.api.protocol.Types.EXPIRY_ACK;
-import static net.sf.briar.api.protocol.Types.EXPIRY_UPDATE;
+import static net.sf.briar.api.protocol.Types.RETENTION_ACK;
+import static net.sf.briar.api.protocol.Types.RETENTION_UPDATE;
 import static net.sf.briar.api.protocol.Types.MESSAGE;
 import static net.sf.briar.api.protocol.Types.OFFER;
 import static net.sf.briar.api.protocol.Types.REQUEST;
@@ -26,8 +26,8 @@ import net.sf.briar.api.Bytes;
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.TransportProperties;
 import net.sf.briar.api.protocol.Ack;
-import net.sf.briar.api.protocol.ExpiryAck;
-import net.sf.briar.api.protocol.ExpiryUpdate;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
 import net.sf.briar.api.protocol.ProtocolReader;
@@ -90,30 +90,6 @@ class ProtocolReaderImpl implements ProtocolReader {
 		return new Ack(Collections.unmodifiableList(acked));
 	}
 
-	public boolean hasExpiryAck() throws IOException {
-		return r.hasStruct(EXPIRY_ACK);
-	}
-
-	public ExpiryAck readExpiryAck() throws IOException {
-		r.readStructId(EXPIRY_ACK);
-		long version = r.readInt64();
-		if(version < 0L) throw new FormatException();
-		return new ExpiryAck(version);
-	}
-
-	public boolean hasExpiryUpdate() throws IOException {
-		return r.hasStruct(EXPIRY_UPDATE);
-	}
-
-	public ExpiryUpdate readExpiryUpdate() throws IOException {
-		r.readStructId(EXPIRY_UPDATE);
-		long expiry = r.readInt64();
-		if(expiry < 0L) throw new FormatException();
-		long version = r.readInt64();
-		if(version < 0L) throw new FormatException();
-		return new ExpiryUpdate(expiry, version);
-	}
-
 	public boolean hasMessage() throws IOException {
 		return r.hasStruct(MESSAGE);
 	}
@@ -173,6 +149,30 @@ class ProtocolReaderImpl implements ProtocolReader {
 		return new Request(b, length);
 	}
 
+	public boolean hasRetentionAck() throws IOException {
+		return r.hasStruct(RETENTION_ACK);
+	}
+
+	public RetentionAck readRetentionAck() throws IOException {
+		r.readStructId(RETENTION_ACK);
+		long version = r.readInt64();
+		if(version < 0L) throw new FormatException();
+		return new RetentionAck(version);
+	}
+
+	public boolean hasRetentionUpdate() throws IOException {
+		return r.hasStruct(RETENTION_UPDATE);
+	}
+
+	public RetentionUpdate readRetentionUpdate() throws IOException {
+		r.readStructId(RETENTION_UPDATE);
+		long retention = r.readInt64();
+		if(retention < 0L) throw new FormatException();
+		long version = r.readInt64();
+		if(version < 0L) throw new FormatException();
+		return new RetentionUpdate(retention, version);
+	}
+
 	public boolean hasSubscriptionAck() throws IOException {
 		return r.hasStruct(SUBSCRIPTION_ACK);
 	}
diff --git a/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java b/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java
index e345aa478f2a057ad4dc487fc823bcd33d330cbf..3e3f2e04c59f125e44be35c4670c9e62f0acad35 100644
--- a/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java
+++ b/briar-core/src/net/sf/briar/protocol/ProtocolWriterImpl.java
@@ -2,8 +2,8 @@ package net.sf.briar.protocol;
 
 import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.protocol.Types.ACK;
-import static net.sf.briar.api.protocol.Types.EXPIRY_ACK;
-import static net.sf.briar.api.protocol.Types.EXPIRY_UPDATE;
+import static net.sf.briar.api.protocol.Types.RETENTION_ACK;
+import static net.sf.briar.api.protocol.Types.RETENTION_UPDATE;
 import static net.sf.briar.api.protocol.Types.GROUP;
 import static net.sf.briar.api.protocol.Types.OFFER;
 import static net.sf.briar.api.protocol.Types.REQUEST;
@@ -17,8 +17,8 @@ import java.io.OutputStream;
 import java.util.BitSet;
 
 import net.sf.briar.api.protocol.Ack;
-import net.sf.briar.api.protocol.ExpiryAck;
-import net.sf.briar.api.protocol.ExpiryUpdate;
+import net.sf.briar.api.protocol.RetentionAck;
+import net.sf.briar.api.protocol.RetentionUpdate;
 import net.sf.briar.api.protocol.Group;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
@@ -74,19 +74,6 @@ class ProtocolWriterImpl implements ProtocolWriter {
 		if(flush) out.flush();
 	}
 
-	public void writeExpiryAck(ExpiryAck a) throws IOException {
-		w.writeStructId(EXPIRY_ACK);
-		w.writeInt64(a.getVersionNumber());
-		if(flush) out.flush();
-	}
-
-	public void writeExpiryUpdate(ExpiryUpdate e) throws IOException {
-		w.writeStructId(EXPIRY_UPDATE);
-		w.writeInt64(e.getExpiryTime());
-		w.writeInt64(e.getVersionNumber());
-		if(flush) out.flush();
-	}
-
 	public void writeMessage(byte[] raw) throws IOException {
 		out.write(raw);
 		if(flush) out.flush();
@@ -120,6 +107,19 @@ class ProtocolWriterImpl implements ProtocolWriter {
 		if(flush) out.flush();
 	}
 
+	public void writeRetentionAck(RetentionAck a) throws IOException {
+		w.writeStructId(RETENTION_ACK);
+		w.writeInt64(a.getVersionNumber());
+		if(flush) out.flush();
+	}
+
+	public void writeRetentionUpdate(RetentionUpdate u) throws IOException {
+		w.writeStructId(RETENTION_UPDATE);
+		w.writeInt64(u.getRetentionTime());
+		w.writeInt64(u.getVersionNumber());
+		if(flush) out.flush();
+	}
+
 	public void writeSubscriptionAck(SubscriptionAck a) throws IOException {
 		w.writeStructId(SUBSCRIPTION_ACK);
 		w.writeInt64(a.getVersionNumber());