diff --git a/api/net/sf/briar/api/db/ConnectionWindow.java b/api/net/sf/briar/api/db/ConnectionWindow.java
new file mode 100644
index 0000000000000000000000000000000000000000..53ed0ed23e7cf425b2879c29c1137c759c7e9e23
--- /dev/null
+++ b/api/net/sf/briar/api/db/ConnectionWindow.java
@@ -0,0 +1,12 @@
+package net.sf.briar.api.db;
+
+public interface ConnectionWindow {
+
+	long getCentre();
+
+	void setCentre(long centre);
+
+	int getBitmap();
+
+	void setBitmap(int bitmap);
+}
diff --git a/components/net/sf/briar/db/ConnectionWindowImpl.java b/components/net/sf/briar/db/ConnectionWindowImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6010a0fc36cdc77119ba472a5e5b145d624e176c
--- /dev/null
+++ b/components/net/sf/briar/db/ConnectionWindowImpl.java
@@ -0,0 +1,30 @@
+package net.sf.briar.db;
+
+import net.sf.briar.api.db.ConnectionWindow;
+
+class ConnectionWindowImpl implements ConnectionWindow {
+
+	private long centre;
+	private int bitmap;
+
+	ConnectionWindowImpl(long centre, int bitmap) {
+		this.centre = centre;
+		this.bitmap = bitmap;
+	}
+
+	public long getCentre() {
+		return centre;
+	}
+
+	public void setCentre(long centre) {
+		this.centre = centre;
+	}
+
+	public int getBitmap() {
+		return bitmap;
+	}
+
+	public void setBitmap(int bitmap) {
+		this.bitmap = bitmap;
+	}
+}
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index 4f794672afae1c991c5eead78bf8e2fefc2962b2..39042ef7ea071671c7cdb463436c3859aa3bbf29 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -5,6 +5,7 @@ import java.util.Map;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.db.ConnectionWindow;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
@@ -155,6 +156,13 @@ interface Database<T> {
 	 */
 	Collection<BatchId> getBatchesToAck(T txn, ContactId c) throws DbException;
 
+	/**
+	 * Returns the connection reordering window for the given contact and
+	 * transport.
+	 */
+	ConnectionWindow getConnectionWindow(T txn, ContactId c, int transport)
+	throws DbException;
+
 	/**
 	 * Returns the IDs of all contacts.
 	 * <p>
@@ -371,6 +379,13 @@ interface Database<T> {
 	 */
 	void removeSubscription(T txn, GroupId g) throws DbException;
 
+	/**
+	 * Sets the connection reordering window for the given contact and
+	 * transport.
+	 */
+	void setConnectionWindow(T txn, ContactId c, int transport,
+			ConnectionWindow w) throws DbException;
+
 	/**
 	 * Sets the user's rating for the given author.
 	 * <p>
diff --git a/components/net/sf/briar/db/H2Database.java b/components/net/sf/briar/db/H2Database.java
index d2902e7e6bbb894a8dc27554f0e47dc2e82a5dad..309255646251c8bc71e75507814328f4509cc0f4 100644
--- a/components/net/sf/briar/db/H2Database.java
+++ b/components/net/sf/briar/db/H2Database.java
@@ -33,7 +33,7 @@ class H2Database extends JdbcDatabase {
 	@Inject
 	H2Database(File dir, @DatabasePassword Password password, long maxSize,
 			GroupFactory groupFactory) {
-		super(groupFactory, "BINARY(32)", "BIGINT", "BINARY");
+		super(groupFactory, "BINARY(32)", "BINARY");
 		home = new File(dir, "db");
 		this.password = password;
 		url = "jdbc:h2:split:" + home.getPath()
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index 716146fd65e81575e5ea3a25ad99d3992cb9ff61..39bdf88b036e368ecdfdc9fd23239d8ae04a6df6 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -20,6 +20,7 @@ import java.util.logging.Logger;
 
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
+import net.sf.briar.api.db.ConnectionWindow;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
@@ -43,7 +44,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		+ " (groupId HASH NOT NULL,"
 		+ " groupName VARCHAR NOT NULL,"
 		+ " groupKey BINARY,"
-		+ " start TIMESTAMP NOT NULL,"
+		+ " start BIGINT NOT NULL,"
 		+ " PRIMARY KEY (groupId))";
 
 	private static final String CREATE_MESSAGES =
@@ -52,7 +53,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		+ " parentId HASH NOT NULL,"
 		+ " groupId HASH NOT NULL,"
 		+ " authorId HASH NOT NULL,"
-		+ " timestamp TIMESTAMP NOT NULL,"
+		+ " timestamp BIGINT NOT NULL,"
 		+ " size INT NOT NULL,"
 		+ " raw BLOB NOT NULL,"
 		+ " sendability INT NOT NULL,"
@@ -66,7 +67,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_MESSAGES_BY_AUTHOR =
 		"CREATE INDEX messagesByAuthor ON messages (authorId)";
 
-	private static final String INDEX_MESSAGES_BY_TIMESTAMP =
+	private static final String INDEX_MESSAGES_BY_BIGINT =
 		"CREATE INDEX messagesByTimestamp ON messages (timestamp)";
 
 	private static final String INDEX_MESSAGES_BY_SENDABILITY =
@@ -75,8 +76,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String CREATE_CONTACTS =
 		"CREATE TABLE contacts"
 		+ " (contactId INT NOT NULL,"
-		+ " subscriptionsTimestamp TIMESTAMP NOT NULL,"
-		+ " transportsTimestamp TIMESTAMP NOT NULL,"
+		+ " subscriptionsTimestamp BIGINT NOT NULL,"
+		+ " transportsTimestamp BIGINT NOT NULL,"
 		+ " PRIMARY KEY (contactId))";
 
 	private static final String CREATE_VISIBILITIES =
@@ -106,7 +107,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		+ " groupId HASH NOT NULL,"
 		+ " groupName VARCHAR NOT NULL,"
 		+ " groupKey BINARY,"
-		+ " start TIMESTAMP NOT NULL,"
+		+ " start BIGINT NOT NULL,"
 		+ " PRIMARY KEY (contactId, groupId),"
 		+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
 		+ " ON DELETE CASCADE)";
@@ -115,7 +116,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		"CREATE TABLE outstandingBatches"
 		+ " (batchId HASH NOT NULL,"
 		+ " contactId INT NOT NULL,"
-		+ " timestamp TIMESTAMP NOT NULL,"
+		+ " timestamp BIGINT NOT NULL,"
 		+ " passover INT NOT NULL,"
 		+ " PRIMARY KEY (batchId, contactId),"
 		+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
@@ -184,11 +185,21 @@ abstract class JdbcDatabase implements Database<Connection> {
 		+ " value VARCHAR NOT NULL,"
 		+ " PRIMARY KEY (transportName, key))";
 
+	private static final String CREATE_CONNECTION_WINDOWS =
+		"CREATE TABLE connectionWindows"
+		+ " (contactId INT NOT NULL,"
+		+ " transportId INT NOT NULL,"
+		+ " centre BIGINT NOT NULL,"
+		+ " bitmap INT NOT NULL,"
+		+ " PRIMARY KEY (contactId, transportId),"
+		+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+		+ " ON DELETE CASCADE)";
+
 	private static final Logger LOG =
 		Logger.getLogger(JdbcDatabase.class.getName());
 
 	// Different database libraries use different names for certain types
-	private final String hashType, timestampType, binaryType;
+	private final String hashType, binaryType;
 	private final GroupFactory groupFactory;
 	private final LinkedList<Connection> connections =
 		new LinkedList<Connection>(); // Locking: self
@@ -199,10 +210,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 	protected abstract Connection createConnection() throws SQLException;
 
 	JdbcDatabase(GroupFactory groupFactory, String hashType,
-			String timestampType, String binaryType) {
+			String binaryType) {
 		this.groupFactory = groupFactory;
 		this.hashType = hashType;
-		this.timestampType = timestampType;
 		this.binaryType = binaryType;
 	}
 
@@ -250,7 +260,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
 			s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
 			s.executeUpdate(INDEX_MESSAGES_BY_AUTHOR);
-			s.executeUpdate(INDEX_MESSAGES_BY_TIMESTAMP);
+			s.executeUpdate(INDEX_MESSAGES_BY_BIGINT);
 			s.executeUpdate(INDEX_MESSAGES_BY_SENDABILITY);
 			s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
 			s.executeUpdate(insertTypeNames(CREATE_VISIBILITIES));
@@ -267,6 +277,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			s.executeUpdate(insertTypeNames(CREATE_CONTACT_TRANSPORTS));
 			s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
 			s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_CONFIG));
+			s.executeUpdate(insertTypeNames(CREATE_CONNECTION_WINDOWS));
 			s.close();
 		} catch(SQLException e) {
 			tryToClose(s);
@@ -276,7 +287,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	private String insertTypeNames(String s) {
 		s = s.replaceAll("HASH", hashType);
-		s = s.replaceAll("TIMESTAMP", timestampType);
 		s = s.replaceAll("BINARY", binaryType);
 		return s;
 	}
@@ -392,19 +402,17 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT COUNT(batchId) FROM batchesToAck"
+			String sql = "SELECT NULL FROM batchesToAck"
 				+ " WHERE batchId = ? AND contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, b.getBytes());
 			ps.setInt(2, c.getInt());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			int count = rs.getInt(1);
-			if(count > 1) throw new DbStateException();
+			boolean found = rs.next();
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			if(count == 1) return;
+			if(found) return;
 			sql = "INSERT INTO batchesToAck (batchId, contactId)"
 				+ " VALUES (?, ?)";
 			ps = txn.prepareStatement(sql);
@@ -589,18 +597,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT COUNT(contactId) FROM contacts"
-				+ " WHERE contactId = ?";
+			String sql = "SELECT NULL FROM contacts WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			int count = rs.getInt(1);
-			if(count > 1) throw new DbStateException();
+			boolean found = rs.next();
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			return count > 0;
+			return found;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -613,18 +618,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT COUNT(messageId) FROM messages"
-				+ " WHERE messageId = ?";
+			String sql = "SELECT NULL FROM messages WHERE messageId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			int count = rs.getInt(1);
-			if(count > 1) throw new DbStateException();
+			boolean found = rs.next();
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			return count > 0;
+			return found;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -637,18 +639,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT COUNT(groupId) FROM subscriptions"
-				+ " WHERE groupId = ?";
+			String sql = "SELECT NULL FROM subscriptions WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getBytes());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			int count = rs.getInt(1);
-			if(count > 1) throw new DbStateException();
+			boolean found = rs.next();
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			return count > 0;
+			return found;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -731,7 +730,36 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<ContactId> getContacts(Connection txn) throws DbException {
+	public ConnectionWindow getConnectionWindow(Connection txn, ContactId c,
+			int transport) throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT centre, bitmap FROM connectionWindows"
+				+ " WHERE contactId = ? AND transportId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setInt(2, transport);
+			rs = ps.executeQuery();
+			long centre = 0L;
+			int bitmap = 0;
+			if(rs.next()) {
+				centre = rs.getLong(1);
+				bitmap = rs.getInt(2);
+				if(rs.next()) throw new DbStateException();
+			}
+			rs.close();
+			ps.close();
+			return new ConnectionWindowImpl(centre, bitmap);
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
+	public Collection<ContactId> getContacts(Connection txn)
+	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -1496,6 +1524,48 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setConnectionWindow(Connection txn, ContactId c, int transport,
+			ConnectionWindow w) throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT NULL FROM connectionWindows"
+				+ " WHERE contactId = ? AND transportId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, c.getInt());
+			ps.setInt(2, transport);
+			rs = ps.executeQuery();
+			if(rs.next()) {
+				if(rs.next()) throw new DbStateException();
+				rs.close();
+				ps.close();
+				sql = "UPDATE connectionWindows SET centre = ?, bitmap = ?"
+					+ " WHERE contactId = ? AND transportId = ?";
+				ps = txn.prepareStatement(sql);
+				int affected = ps.executeUpdate();
+				if(affected != 1) throw new DbStateException();
+			} else {
+				rs.close();
+				ps.close();
+				sql = "INSERT INTO connectionWindows"
+					+ " (contactId, transportId, centre, bitmap)"
+					+ " VALUES(?, ?, ?, ?)";
+				ps = txn.prepareStatement(sql);
+				ps.setInt(1, c.getInt());
+				ps.setInt(2, transport);
+				ps.setLong(3, w.getCentre());
+				ps.setInt(4, w.getBitmap());
+				int affected = ps.executeUpdate();
+				if(affected != 1) throw new DbStateException();
+			}
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public Rating setRating(Connection txn, AuthorId a, Rating r)
 	throws DbException {
 		PreparedStatement ps = null;
@@ -1608,7 +1678,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT COUNT(messages.messageId) FROM messages"
+			String sql = "SELECT NULL FROM messages"
 				+ " JOIN contactSubscriptions"
 				+ " ON messages.groupId = contactSubscriptions.groupId"
 				+ " JOIN visibilities"
@@ -1621,13 +1691,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.setInt(2, c.getInt());
 			ps.setInt(3, c.getInt());
 			rs = ps.executeQuery();
-			if(!rs.next()) throw new DbStateException();
-			int count = rs.getInt(1);
-			if(count > 1) throw new DbStateException();
+			boolean found = rs.next();
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			if(count == 0) return false;
+			if(!found) return false;
 			sql = "UPDATE statuses SET status = ?"
 				+ " WHERE messageId = ? AND contactId = ?";
 			ps = txn.prepareStatement(sql);
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 1514006f029d31cda1e21b40ca67e445dfbc2aa6..95021acd29a62f334751c8113efeb2790191a559 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -17,6 +17,7 @@ import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
 import net.sf.briar.api.crypto.Password;
+import net.sf.briar.api.db.ConnectionWindow;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
@@ -1204,6 +1205,61 @@ public class H2DatabaseTest extends TestCase {
 		// Make the group invisible again
 		db.setVisibility(txn, groupId, Collections.<ContactId>emptySet());
 		assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
+
+		db.commitTransaction(txn);
+		db.close();
+	}
+
+	@Test
+	public void testGettingUnknownConnectionWindowReturnsDefault()
+	throws DbException {
+		Database<Connection> db = open(false);
+		Connection txn = db.startTransaction();
+
+		// Add a contact
+		assertEquals(contactId, db.addContact(txn, transports));
+
+		// Get the connection window for a new transport
+		ConnectionWindow w = db.getConnectionWindow(txn, contactId, 123);
+
+		// The connection window should exist and be in the initial state
+		assertNotNull(w);
+		assertEquals(0L, w.getCentre());
+		assertEquals(0, w.getBitmap());
+
+		db.commitTransaction(txn);
+		db.close();
+	}
+
+	@Test
+	public void testConnectionWindow() throws DbException {
+		Database<Connection> db = open(false);
+		Connection txn = db.startTransaction();
+
+		// Add a contact
+		assertEquals(contactId, db.addContact(txn, transports));
+
+		// Get the connection window for a new transport
+		ConnectionWindow w = db.getConnectionWindow(txn, contactId, 123);
+
+		// The connection window should exist and be in the initial state
+		assertNotNull(w);
+		assertEquals(0L, w.getCentre());
+		assertEquals(0, w.getBitmap());
+
+		// Update the connection window and store it
+		w.setCentre(1);
+		w.setBitmap(0x00008000);
+		db.setConnectionWindow(txn, contactId, 123, w);
+
+		// Check that the connection window was stored
+		w = db.getConnectionWindow(txn, contactId, 123);
+		assertNotNull(w);
+		assertEquals(1L, w.getCentre());
+		assertEquals(0x00008000, w.getBitmap());
+
+		db.commitTransaction(txn);
+		db.close();
 	}
 
 	@Test