diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
index a2725ef2d9a0205230211a54fbac230742fe5cbb..c4fdf88a3fc204272d0d8fc9d8df61b94779ca71 100644
--- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
@@ -55,8 +55,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 			new IncomingAuthenticationLayerImpl(correction, mac, macKey);
 		// No reordering or retransmission
 		IncomingReliabilityLayer reliability =
-			new IncomingReliabilityLayerImpl(authentication,
-					new NullFrameWindow());
+			new NullIncomingReliabilityLayer(authentication);
 		// Create the reader - don't tolerate errors
 		return new ConnectionReaderImpl(reliability, false);
 	}
@@ -93,8 +92,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 			new IncomingAuthenticationLayerImpl(correction, mac, macKey);
 		// No reordering or retransmission
 		IncomingReliabilityLayer reliability =
-			new IncomingReliabilityLayerImpl(authentication,
-					new NullFrameWindow());
+			new NullIncomingReliabilityLayer(authentication);
 		// Create the reader - don't tolerate errors
 		return new ConnectionReaderImpl(reliability, false);
 	}
diff --git a/components/net/sf/briar/transport/ConnectionReaderImpl.java b/components/net/sf/briar/transport/ConnectionReaderImpl.java
index 1859eda8dca6aec5f2987257af8b3544310fb6d5..efebd6b301e1a1b1db18576e093e1e6a02d5279c 100644
--- a/components/net/sf/briar/transport/ConnectionReaderImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderImpl.java
@@ -12,14 +12,13 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 
 	private final IncomingReliabilityLayer in;
 	private final boolean tolerateErrors;
-	private final Frame frame;
 
+	private Frame frame;
 	private int offset = 0, length = 0;
 
 	ConnectionReaderImpl(IncomingReliabilityLayer in, boolean tolerateErrors) {
 		this.in = in;
 		this.tolerateErrors = tolerateErrors;
-		frame = new Frame(in.getMaxFrameLength());
 	}
 
 	public InputStream getInputStream() {
@@ -28,7 +27,8 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 
 	@Override
 	public int read() throws IOException {
-		while(length == 0) if(!readValidFrame()) return -1;
+		if(length == -1) return -1;
+		while(length == 0) if(!readFrame()) return -1;
 		int b = frame.getBuffer()[offset] & 0xff;
 		offset++;
 		length--;
@@ -42,7 +42,8 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 
 	@Override
 	public int read(byte[] b, int off, int len) throws IOException {
-		while(length == 0) if(!readValidFrame()) return -1;
+		if(length == -1) return -1;
+		while(length == 0) if(!readFrame()) return -1;
 		len = Math.min(len, length);
 		System.arraycopy(frame.getBuffer(), offset, b, off, len);
 		offset += len;
@@ -50,11 +51,15 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 		return len;
 	}
 
-	private boolean readValidFrame() throws IOException {
+	private boolean readFrame() throws IOException {
 		assert length == 0;
 		while(true) {
 			try {
-				if(!in.readFrame(frame)) return false;
+				frame = in.readFrame(frame);
+				if(frame == null) {
+					length = -1;
+					return false;
+				}
 				offset = FRAME_HEADER_LENGTH;
 				length = HeaderEncoder.getPayloadLength(frame.getBuffer());
 				return true;
diff --git a/components/net/sf/briar/transport/Frame.java b/components/net/sf/briar/transport/Frame.java
index 921e5ff23e6d1d553b06f09c4bdd67208a2d827b..6d1aae7cbdda6904942f0d43d5fc980cd63066e5 100644
--- a/components/net/sf/briar/transport/Frame.java
+++ b/components/net/sf/briar/transport/Frame.java
@@ -26,6 +26,11 @@ class Frame {
 		return buf;
 	}
 
+	public long getFrameNumber() {
+		if(length == -1) throw new IllegalStateException();
+		return HeaderEncoder.getFrameNumber(buf);
+	}
+
 	public int getLength() {
 		if(length == -1) throw new IllegalStateException();
 		return length;
diff --git a/components/net/sf/briar/transport/IncomingReliabilityLayer.java b/components/net/sf/briar/transport/IncomingReliabilityLayer.java
index 162a228c2dc99dd79f6cb3a5d0932a19ae213ab8..01525c0e4bed2714e727769d8513860fb36dc382 100644
--- a/components/net/sf/briar/transport/IncomingReliabilityLayer.java
+++ b/components/net/sf/briar/transport/IncomingReliabilityLayer.java
@@ -5,14 +5,14 @@ import java.io.IOException;
 interface IncomingReliabilityLayer {
 
 	/**
-	 * Reads a frame into the given buffer. Returns false if no more frames
-	 * can be read from the connection.
+	 * Reads and returns a frame, possibly using the given buffer. Returns null
+	 * if no more frames can be read from the connection.
 	 * @throws IOException if an unrecoverable error occurs and the connection
 	 * must be closed.
 	 * @throws InvalidDataException if a recoverable error occurs. The caller
 	 * may choose whether to retry the read or close the connection.
 	 */
-	boolean readFrame(Frame f) throws IOException, InvalidDataException;
+	Frame readFrame(Frame f) throws IOException, InvalidDataException;
 
 	/** Returns the maximum length in bytes of the frames this layer returns. */
 	int getMaxFrameLength();
diff --git a/components/net/sf/briar/transport/IncomingReliabilityLayerImpl.java b/components/net/sf/briar/transport/IncomingReliabilityLayerImpl.java
index a5943a50abe1dfad150cdfaa1e872fad59b5d7f6..5635d0313cac6ee32c25f209a8872c04f98b6013 100644
--- a/components/net/sf/briar/transport/IncomingReliabilityLayerImpl.java
+++ b/components/net/sf/briar/transport/IncomingReliabilityLayerImpl.java
@@ -1,25 +1,69 @@
 package net.sf.briar.transport;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.ListIterator;
 
 class IncomingReliabilityLayerImpl implements IncomingReliabilityLayer {
 
 	private final IncomingAuthenticationLayer in;
-	private final FrameWindow window;
 	private final int maxFrameLength;
+	private final FrameWindow window;
+	private final LinkedList<Frame> frames;
+	private final ArrayList<Frame> freeFrames;
+
+	private long nextFrameNumber = 0L;
 
-	IncomingReliabilityLayerImpl(IncomingAuthenticationLayer in,
-			FrameWindow window) {
+	IncomingReliabilityLayerImpl(IncomingAuthenticationLayer in) {
 		this.in = in;
-		this.window = window;
 		maxFrameLength = in.getMaxFrameLength();
+		window = new FrameWindowImpl();
+		frames = new LinkedList<Frame>();
+		freeFrames = new ArrayList<Frame>();
 	}
 
-	public boolean readFrame(Frame f) throws IOException, InvalidDataException {
-		if(!in.readFrame(f, window)) return false;
-		long frameNumber = HeaderEncoder.getFrameNumber(f.getBuffer());
-		if(!window.remove(frameNumber)) throw new IllegalStateException();
-		return true;
+	public Frame readFrame(Frame f) throws IOException,
+	InvalidDataException {
+		freeFrames.add(f);
+		// Read frames until there's an in-order frame to return
+		Frame next = frames.peek();
+		while(next == null || next.getFrameNumber() > nextFrameNumber) {
+			// Grab a free frame, or allocate one if necessary
+			int free = freeFrames.size();
+			if(free == 0) f = new Frame(maxFrameLength);
+			else f = freeFrames.remove(free - 1);
+			// Read a frame
+			if(!in.readFrame(f, window)) return null;
+			// If the frame is in order, return it
+			long frameNumber = f.getFrameNumber();
+			if(frameNumber == nextFrameNumber) {
+				nextFrameNumber++;
+				return f;
+			}
+			// Insert the frame into the list
+			if(next == null || next.getFrameNumber() > frameNumber) {
+				frames.push(f);
+			} else {
+				boolean inserted = false;
+				ListIterator<Frame> it = frames.listIterator();
+				while(it.hasNext()) {
+					if(it.next().getFrameNumber() > frameNumber) {
+						// Insert the frame before the one just examined
+						it.previous();
+						it.add(f);
+						inserted = true;
+						break;
+					}
+				}
+				if(!inserted) frames.add(f);
+			}
+			next = frames.peek();
+		}
+		assert next != null && next.getFrameNumber() == nextFrameNumber;
+		frames.poll();
+		nextFrameNumber++;
+		return next;
 	}
 
 	public int getMaxFrameLength() {
diff --git a/components/net/sf/briar/transport/NullIncomingReliabilityLayer.java b/components/net/sf/briar/transport/NullIncomingReliabilityLayer.java
new file mode 100644
index 0000000000000000000000000000000000000000..194df4f4bf1517fb41870ab0afde64cde7a25450
--- /dev/null
+++ b/components/net/sf/briar/transport/NullIncomingReliabilityLayer.java
@@ -0,0 +1,27 @@
+package net.sf.briar.transport;
+
+import java.io.IOException;
+
+class NullIncomingReliabilityLayer implements IncomingReliabilityLayer {
+
+	private final IncomingAuthenticationLayer in;
+	private final int maxFrameLength;
+	private final FrameWindow window;
+
+	NullIncomingReliabilityLayer(IncomingAuthenticationLayer in) {
+		this.in = in;
+		maxFrameLength = in.getMaxFrameLength();
+		window = new NullFrameWindow();
+	}
+
+	public Frame readFrame(Frame f) throws IOException, InvalidDataException {
+		if(!in.readFrame(f, window)) return null;
+		if(!window.remove(f.getFrameNumber()))
+			throw new IllegalStateException();
+		return f;
+	}
+
+	public int getMaxFrameLength() {
+		return maxFrameLength;
+	}
+}
diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java
index 3559501c4d093a09b0b508896da53da268f9d814..feaddea5ce670569c7afed45d770f8f404dcf1c0 100644
--- a/test/net/sf/briar/transport/ConnectionReaderImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionReaderImplTest.java
@@ -224,8 +224,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		IncomingAuthenticationLayer authentication =
 			new IncomingAuthenticationLayerImpl(correction, mac, macKey);
 		IncomingReliabilityLayer reliability =
-			new IncomingReliabilityLayerImpl(authentication,
-					new NullFrameWindow());
+			new NullIncomingReliabilityLayer(authentication);
 		return new ConnectionReaderImpl(reliability, false);
 	}
 }
diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java
index 98f50444db1624539188e9c96269351b6963142c..5ca19087826e3778623d9121ee9096b789350435 100644
--- a/test/net/sf/briar/transport/FrameReadWriteTest.java
+++ b/test/net/sf/briar/transport/FrameReadWriteTest.java
@@ -103,8 +103,7 @@ public class FrameReadWriteTest extends BriarTestCase {
 		IncomingAuthenticationLayer authenticationIn =
 			new IncomingAuthenticationLayerImpl(correctionIn, mac, macKey);
 		IncomingReliabilityLayer reliabilityIn =
-			new IncomingReliabilityLayerImpl(authenticationIn,
-					new NullFrameWindow());
+			new NullIncomingReliabilityLayer(authenticationIn);
 		ConnectionReader reader = new ConnectionReaderImpl(reliabilityIn,
 				false);
 		InputStream in1 = reader.getInputStream();