diff --git a/api/net/sf/briar/api/plugins/Segment.java b/api/net/sf/briar/api/plugins/Segment.java
index 61c0a3ed2dec6ae98f63b90948db7c0f55868af5..b0eacf91045e4a4f6717036e8f2de1eb8d4c1881 100644
--- a/api/net/sf/briar/api/plugins/Segment.java
+++ b/api/net/sf/briar/api/plugins/Segment.java
@@ -2,8 +2,6 @@ package net.sf.briar.api.plugins;
 
 public interface Segment {
 
-	void clear();
-
 	byte[] getBuffer();
 
 	int getLength();
diff --git a/components/net/sf/briar/crypto/CryptoComponentImpl.java b/components/net/sf/briar/crypto/CryptoComponentImpl.java
index b1ae497099ae6488de34edd564cd2bb858cd36ae..a12dfb2d7b0a28105f1d71516b1ce54f1ec68150 100644
--- a/components/net/sf/briar/crypto/CryptoComponentImpl.java
+++ b/components/net/sf/briar/crypto/CryptoComponentImpl.java
@@ -30,9 +30,10 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final String CIPHER_ALGO = "AES/CTR/NoPadding";
 	private static final String SECRET_KEY_ALGO = "AES";
 	private static final int SECRET_KEY_BYTES = 32; // 256 bits
+	private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
 	private static final String MAC_ALGO = "HMacSHA256";
 	private static final String SIGNATURE_ALGO = "ECDSA";
-	private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
+	private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
 
 	// Labels for key derivation, null-terminated
 	private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E', 0 };
@@ -176,7 +177,7 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	public Cipher getTagCipher() {
 		try {
-			return Cipher.getInstance(CIPHER_ALGO, PROVIDER);
+			return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
 		} catch(GeneralSecurityException e) {
 			throw new RuntimeException(e);
 		}
diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
index 350eef73a34deebd53c606cae5ca95feb367a919..6e0677abaf3fd9a9384ef8c683c9fe999dbe09f2 100644
--- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
@@ -27,9 +27,9 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 		// Validate the tag
 		Cipher tagCipher = crypto.getTagCipher();
 		ErasableKey tagKey = crypto.deriveTagKey(secret, true);
-		boolean valid = TagEncoder.validateTag(tag, 0, tagCipher, tagKey);
+		long segmentNumber = TagEncoder.decodeTag(tag, tagCipher, tagKey);
 		tagKey.erase();
-		if(!valid) throw new IllegalArgumentException();
+		if(segmentNumber != 0) throw new IllegalArgumentException();
 		return createConnectionReader(in, true, secret);
 	}
 
@@ -51,7 +51,10 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 		Mac mac = crypto.getMac();
 		IncomingEncryptionLayer decrypter = new IncomingEncryptionLayerImpl(in,
 				tagCipher, frameCipher, tagKey, frameKey, false);
+		// No error correction
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
 		// Create the reader
-		return new ConnectionReaderImpl(decrypter, mac, macKey);
+		return new ConnectionReaderImpl(correcter, mac, macKey);
 	}
 }
diff --git a/components/net/sf/briar/transport/ConnectionReaderImpl.java b/components/net/sf/briar/transport/ConnectionReaderImpl.java
index 1afa4ab3780fc26fa0815b849318192bc121c548..0241369d4559a5e3c005dad9fe18657bfc0ebb33 100644
--- a/components/net/sf/briar/transport/ConnectionReaderImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderImpl.java
@@ -2,12 +2,13 @@ package net.sf.briar.transport;
 
 import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
-import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.InvalidKeyException;
+import java.util.Collection;
+import java.util.Collections;
 
 import javax.crypto.Mac;
 
@@ -17,16 +18,16 @@ import net.sf.briar.api.transport.ConnectionReader;
 
 class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 
-	private final IncomingEncryptionLayer decrypter;
+	private final IncomingErrorCorrectionLayer in;
 	private final Mac mac;
-	private final byte[] buf;
+	private final Frame frame;
 
-	private long frame = 0L;
+	private long frameNumber = 0L;
 	private int offset = 0, length = 0;
 
-	ConnectionReaderImpl(IncomingEncryptionLayer decrypter, Mac mac,
+	ConnectionReaderImpl(IncomingErrorCorrectionLayer in, Mac mac,
 			ErasableKey macKey) {
-		this.decrypter = decrypter;
+		this.in = in;
 		this.mac = mac;
 		// Initialise the MAC
 		try {
@@ -37,7 +38,7 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 		macKey.erase();
 		if(mac.getMacLength() != MAC_LENGTH)
 			throw new IllegalArgumentException();
-		buf = new byte[MAX_FRAME_LENGTH];
+		frame = new Frame();
 	}
 
 	public InputStream getInputStream() {
@@ -47,7 +48,7 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 	@Override
 	public int read() throws IOException {
 		while(length == 0) if(!readFrame()) return -1;
-		int b = buf[offset] & 0xff;
+		int b = frame.getBuffer()[offset] & 0xff;
 		offset++;
 		length--;
 		return b;
@@ -62,7 +63,7 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 	public int read(byte[] b, int off, int len) throws IOException {
 		while(length == 0) if(!readFrame()) return -1;
 		len = Math.min(len, length);
-		System.arraycopy(buf, offset, b, off, len);
+		System.arraycopy(frame.getBuffer(), offset, b, off, len);
 		offset += len;
 		length -= len;
 		return len;
@@ -71,17 +72,19 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 	private boolean readFrame() throws IOException {
 		assert length == 0;
 		// Don't allow more than 2^32 frames to be read
-		if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
+		if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
 		// Read a frame
-		int frameLength = decrypter.readFrame(buf);
-		if(frameLength == -1) return false;
+		Collection<Long> window = Collections.singleton(frameNumber);
+		if(!in.readFrame(frame, window)) return false;
 		// Check that the frame number is correct and the length is legal
-		if(!HeaderEncoder.validateHeader(buf, frame))
+		byte[] buf = frame.getBuffer();
+		if(!HeaderEncoder.validateHeader(buf, frameNumber))
 			throw new FormatException();
+		// Check that the payload and padding lengths are correct
 		int payload = HeaderEncoder.getPayloadLength(buf);
 		int padding = HeaderEncoder.getPaddingLength(buf);
-		if(frameLength != FRAME_HEADER_LENGTH + payload + padding + MAC_LENGTH)
-			throw new FormatException();
+		if(frame.getLength() != FRAME_HEADER_LENGTH + payload + padding
+				+ MAC_LENGTH) throw new FormatException();
 		// Check that the padding is all zeroes
 		int paddingStart = FRAME_HEADER_LENGTH + payload;
 		for(int i = paddingStart; i < paddingStart + padding; i++) {
@@ -96,7 +99,7 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 		}
 		offset = FRAME_HEADER_LENGTH;
 		length = payload;
-		frame++;
+		frameNumber++;
 		return true;
 	}
 }
diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
index d00a4efd1de1b187f0dda9dd150278efea0d3fdb..68cfe03cd6ad7b998a0a999c884dd95d35f0c243 100644
--- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
+++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
@@ -103,7 +103,7 @@ DatabaseListener {
 	private Bytes calculateTag(Context ctx, byte[] secret) {
 		ErasableKey tagKey = crypto.deriveTagKey(secret, true);
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
 		tagKey.erase();
 		return new Bytes(tag);
 	}
diff --git a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
index 3af3e0a56ad096d252138843d5bfbf658d2599c7..85daf0053a42667e7286022b39473bbbfd617af4 100644
--- a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
@@ -29,12 +29,12 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
 
 	public ConnectionWriter createConnectionWriter(OutputStream out,
 			long capacity, byte[] secret, byte[] tag) {
-		// Decrypt the tag
+		// Validate the tag
 		Cipher tagCipher = crypto.getTagCipher();
 		ErasableKey tagKey = crypto.deriveTagKey(secret, true);
-		boolean valid = TagEncoder.validateTag(tag, 0, tagCipher, tagKey);
+		long segmentNumber = TagEncoder.decodeTag(tag, tagCipher, tagKey);
 		tagKey.erase();
-		if(!valid) throw new IllegalArgumentException();
+		if(segmentNumber != 0) throw new IllegalArgumentException();
 		return createConnectionWriter(out, capacity, false, secret);
 	}
 
diff --git a/components/net/sf/briar/transport/Frame.java b/components/net/sf/briar/transport/Frame.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9d3ca2d1ba346941847556dee5e8a08f9049bcf
--- /dev/null
+++ b/components/net/sf/briar/transport/Frame.java
@@ -0,0 +1,25 @@
+package net.sf.briar.transport;
+
+import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
+
+class Frame {
+
+	private final byte[] buf = new byte[MAX_FRAME_LENGTH];
+
+	private int length = -1;
+
+	public byte[] getBuffer() {
+		return buf;
+	}
+
+	public int getLength() {
+		if(length == -1) throw new IllegalStateException();
+		return length;
+	}
+
+	public void setLength(int length) {
+		if(length < 0 || length > buf.length)
+			throw new IllegalArgumentException();
+		this.length = length;
+	}
+}
diff --git a/components/net/sf/briar/transport/IncomingEncryptionLayer.java b/components/net/sf/briar/transport/IncomingEncryptionLayer.java
index bd4bd19829a000454397d49badc2bd9e7ce14d9a..c93681130c13ec5191952c0ae19f323e7b87eb42 100644
--- a/components/net/sf/briar/transport/IncomingEncryptionLayer.java
+++ b/components/net/sf/briar/transport/IncomingEncryptionLayer.java
@@ -2,11 +2,13 @@ package net.sf.briar.transport;
 
 import java.io.IOException;
 
+import net.sf.briar.api.plugins.Segment;
+
 interface IncomingEncryptionLayer {
 
 	/**
-	 * Reads a frame into the given buffer and returns its length, or -1 if no
-	 * more frames can be read.
+	 * Reads a segment, excluding its tag, into the given buffer. Returns false
+	 * if no more segments can be read from the connection.
 	 */
-	int readFrame(byte[] b) throws IOException;
+	boolean readSegment(Segment s) throws IOException;
 }
diff --git a/components/net/sf/briar/transport/IncomingEncryptionLayerImpl.java b/components/net/sf/briar/transport/IncomingEncryptionLayerImpl.java
index 21d64a3744ee7629aa851e519b5d08200176d96b..cdfd11e99526281e2b9b46df6c13d90b13a8cd46 100644
--- a/components/net/sf/briar/transport/IncomingEncryptionLayerImpl.java
+++ b/components/net/sf/briar/transport/IncomingEncryptionLayerImpl.java
@@ -2,8 +2,9 @@ package net.sf.briar.transport;
 
 import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
+import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
+import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
-import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -15,6 +16,7 @@ import javax.crypto.spec.IvParameterSpec;
 
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.Segment;
 
 class IncomingEncryptionLayerImpl implements IncomingEncryptionLayer {
 
@@ -22,10 +24,11 @@ class IncomingEncryptionLayerImpl implements IncomingEncryptionLayer {
 	private final Cipher tagCipher, frameCipher;
 	private final ErasableKey tagKey, frameKey;
 	private final int blockSize;
-	private final byte[] iv;
+	private final byte[] iv, ciphertext;
 	private final boolean tagEverySegment;
 
-	private long frame = 0L;
+	private boolean firstSegment = true;
+	private long segmentNumber = 0L;
 
 	IncomingEncryptionLayerImpl(InputStream in, Cipher tagCipher,
 			Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey,
@@ -40,71 +43,73 @@ class IncomingEncryptionLayerImpl implements IncomingEncryptionLayer {
 		if(blockSize < FRAME_HEADER_LENGTH)
 			throw new IllegalArgumentException();
 		iv = IvEncoder.encodeIv(0, blockSize);
+		ciphertext = new byte[MAX_SEGMENT_LENGTH];
 	}
 
-	public int readFrame(byte[] b) throws IOException {
-		if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
-		boolean tag = tagEverySegment && frame > 0;
-		// Clear the buffer before exposing it to the transport plugin
-		for(int i = 0; i < b.length; i++) b[i] = 0;
+	public boolean readSegment(Segment s) throws IOException {
+		boolean tag = tagEverySegment && !firstSegment;
 		try {
 			// If a tag is expected then read, decrypt and validate it
 			if(tag) {
 				int offset = 0;
 				while(offset < TAG_LENGTH) {
-					int read = in.read(b, offset, TAG_LENGTH - offset);
+					int read = in.read(ciphertext, offset, TAG_LENGTH - offset);
 					if(read == -1) {
-						if(offset == 0) return -1;
+						if(offset == 0) return false;
 						throw new EOFException();
 					}
 					offset += read;
 				}
-				if(!TagEncoder.validateTag(b, frame, tagCipher, tagKey))
-					throw new FormatException();
+				long seg = TagEncoder.decodeTag(ciphertext, tagCipher, tagKey);
+				if(seg == -1) throw new FormatException();
+				segmentNumber = seg;
 			}
-			// Read the first block
+			// Read the first block of the frame/segment
 			int offset = 0;
 			while(offset < blockSize) {
-				int read = in.read(b, offset, blockSize - offset);
+				int read = in.read(ciphertext, offset, blockSize - offset);
 				if(read == -1) {
-					if(offset == 0 && !tag) return -1;
+					if(offset == 0 && !tag && !firstSegment) return false;
 					throw new EOFException();
 				}
 				offset += read;
 			}
-			// Decrypt the first block
+			// Decrypt the first block of the frame/segment
+			byte[] plaintext = s.getBuffer();
 			try {
-				IvEncoder.updateIv(iv, frame);
+				IvEncoder.updateIv(iv, segmentNumber);
 				IvParameterSpec ivSpec = new IvParameterSpec(iv);
 				frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
-				int decrypted = frameCipher.update(b, 0, blockSize, b);
+				int decrypted = frameCipher.update(ciphertext, 0, blockSize,
+						plaintext);
 				if(decrypted != blockSize) throw new RuntimeException();
 			} catch(GeneralSecurityException badCipher) {
 				throw new RuntimeException(badCipher);
 			}
-			// Validate and parse the header
-			if(!HeaderEncoder.validateHeader(b, frame))
-				throw new FormatException();
-			int payload = HeaderEncoder.getPayloadLength(b);
-			int padding = HeaderEncoder.getPaddingLength(b);
+			// Parse the frame header
+			int payload = HeaderEncoder.getPayloadLength(plaintext);
+			int padding = HeaderEncoder.getPaddingLength(plaintext);
 			int length = FRAME_HEADER_LENGTH + payload + padding + MAC_LENGTH;
-			// Read the remainder of the frame
+			if(length > MAX_FRAME_LENGTH) throw new FormatException();
+			// Read the remainder of the frame/segment
 			while(offset < length) {
-				int read = in.read(b, offset, length - offset);
+				int read = in.read(ciphertext, offset, length - offset);
 				if(read == -1) throw new EOFException();
 				offset += read;
 			}
-			// Decrypt the remainder of the frame
+			// Decrypt the remainder of the frame/segment
 			try {
-				int decrypted = frameCipher.doFinal(b, blockSize,
-						length - blockSize, b, blockSize);
+				int decrypted = frameCipher.doFinal(ciphertext, blockSize,
+						length - blockSize, plaintext, blockSize);
 				if(decrypted != length - blockSize)
 					throw new RuntimeException();
 			} catch(GeneralSecurityException badCipher) {
 				throw new RuntimeException(badCipher);
 			}
-			frame++;
-			return length;
+			s.setLength(length);
+			s.setSegmentNumber(segmentNumber++);
+			firstSegment = false;
+			return true;
 		} catch(IOException e) {
 			frameKey.erase();
 			tagKey.erase();
diff --git a/components/net/sf/briar/transport/IncomingErrorCorrectionLayer.java b/components/net/sf/briar/transport/IncomingErrorCorrectionLayer.java
new file mode 100644
index 0000000000000000000000000000000000000000..446c7a3ef52d5f64beccf4574702f54eb36e2a7c
--- /dev/null
+++ b/components/net/sf/briar/transport/IncomingErrorCorrectionLayer.java
@@ -0,0 +1,14 @@
+package net.sf.briar.transport;
+
+import java.io.IOException;
+import java.util.Collection;
+
+interface IncomingErrorCorrectionLayer {
+
+	/**
+	 * Reads a frame into the given buffer. The frame number must be contained
+	 * in the given window. Returns false if no more frames can be read from
+	 * the connection.
+	 */
+	boolean readFrame(Frame f, Collection<Long> window) throws IOException;
+}
diff --git a/components/net/sf/briar/transport/IncomingSegmentedEncryptionLayer.java b/components/net/sf/briar/transport/IncomingSegmentedEncryptionLayer.java
index 61af30b988eed27bc9a97dc7c3ca1a4c54db6b3d..00652bd49ebb15a6e27955f24857968bac90b282 100644
--- a/components/net/sf/briar/transport/IncomingSegmentedEncryptionLayer.java
+++ b/components/net/sf/briar/transport/IncomingSegmentedEncryptionLayer.java
@@ -4,7 +4,6 @@ import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
-import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -27,7 +26,8 @@ class IncomingSegmentedEncryptionLayer implements IncomingEncryptionLayer {
 	private final Segment segment;
 	private final boolean tagEverySegment;
 
-	private long frame = 0L;
+	private boolean firstSegment = true;
+	private long segmentNumber = 0L;
 
 	IncomingSegmentedEncryptionLayer(SegmentSource in, Cipher tagCipher,
 			Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey,
@@ -45,41 +45,37 @@ class IncomingSegmentedEncryptionLayer implements IncomingEncryptionLayer {
 		segment = new SegmentImpl();
 	}
 
-	public int readFrame(byte[] b) throws IOException {
-		if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
-		boolean tag = tagEverySegment && frame > 0;
-		// Clear the buffer before exposing it to the transport plugin
-		segment.clear();
+	public boolean readSegment(Segment s) throws IOException {
+		boolean tag = tagEverySegment && !firstSegment;
 		try {
 			// Read the segment
-			if(!in.readSegment(segment)) return -1;
+			if(!in.readSegment(segment)) return false;
 			int offset = tag ? TAG_LENGTH : 0, length = segment.getLength();
 			if(length > MAX_SEGMENT_LENGTH) throw new FormatException();
 			if(length < offset + FRAME_HEADER_LENGTH + MAC_LENGTH)
 				throw new FormatException();
-			// If a tag is expected, decrypt and validate it
-			if(tag && !TagEncoder.validateTag(segment.getBuffer(), frame,
-					tagCipher, tagKey)) throw new FormatException();
-			// Decrypt the frame
+			byte[] ciphertext = segment.getBuffer();
+			// If a tag is expected then decrypt and validate it
+			if(tag) {
+				long seg = TagEncoder.decodeTag(ciphertext, tagCipher, tagKey);
+				if(seg == -1) throw new FormatException();
+				segmentNumber = seg;
+			}
+			// Decrypt the segment
 			try {
-				IvEncoder.updateIv(iv, frame);
+				IvEncoder.updateIv(iv, segmentNumber);
 				IvParameterSpec ivSpec = new IvParameterSpec(iv);
 				frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
-				int decrypted = frameCipher.doFinal(segment.getBuffer(), offset,
-						length - offset, b);
+				int decrypted = frameCipher.doFinal(ciphertext, offset,
+						length - offset, s.getBuffer());
 				if(decrypted != length - offset) throw new RuntimeException();
 			} catch(GeneralSecurityException badCipher) {
 				throw new RuntimeException(badCipher);
 			}
-			// Validate and parse the header
-			if(!HeaderEncoder.validateHeader(b, frame))
-				throw new FormatException();
-			int payload = HeaderEncoder.getPayloadLength(b);
-			int padding = HeaderEncoder.getPaddingLength(b);
-			if(length != offset + FRAME_HEADER_LENGTH + payload + padding
-					+ MAC_LENGTH) throw new FormatException();
-			frame++;
-			return length - offset;
+			s.setLength(length - offset);
+			s.setSegmentNumber(segmentNumber++);
+			firstSegment = false;
+			return true;
 		} catch(IOException e) {
 			frameKey.erase();
 			tagKey.erase();
diff --git a/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java b/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd97383c7c08bebd7f153bf0219d741756a6c47f
--- /dev/null
+++ b/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java
@@ -0,0 +1,31 @@
+package net.sf.briar.transport;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import net.sf.briar.api.plugins.Segment;
+
+class NullIncomingErrorCorrectionLayer implements IncomingErrorCorrectionLayer {
+
+	private final IncomingEncryptionLayer in;
+	private final Segment segment;
+
+	NullIncomingErrorCorrectionLayer(IncomingEncryptionLayer in) {
+		this.in = in;
+		segment = new SegmentImpl();
+	}
+
+	public boolean readFrame(Frame f, Collection<Long> window)
+	throws IOException {
+		while(true) {
+			if(!in.readSegment(segment)) return false;
+			byte[] buf = segment.getBuffer();
+			if(window.contains(HeaderEncoder.getFrameNumber(buf))) break;
+		}
+		int length = segment.getLength();
+		// FIXME: Unnecessary copy
+		System.arraycopy(segment.getBuffer(), 0, f.getBuffer(), 0, length);
+		f.setLength(length);
+		return true;
+	}
+}
diff --git a/components/net/sf/briar/transport/SegmentImpl.java b/components/net/sf/briar/transport/SegmentImpl.java
index 0ad809bdc0838e86bf82c0b89d3ba9e5ce4a4642..da3d4eab391387085857776978dc219a494b4fd9 100644
--- a/components/net/sf/briar/transport/SegmentImpl.java
+++ b/components/net/sf/briar/transport/SegmentImpl.java
@@ -11,12 +11,6 @@ class SegmentImpl implements Segment {
 	private int length = -1;
 	private long segmentNumber = -1;
 
-	public void clear() {
-		for(int i = 0; i < buf.length; i++) buf[i] = 0;
-		length = -1;
-		segmentNumber = -1;
-	}
-
 	public byte[] getBuffer() {
 		return buf;
 	}
diff --git a/components/net/sf/briar/transport/TagEncoder.java b/components/net/sf/briar/transport/TagEncoder.java
index 6afa2bead72d06f19190096c3dbc16bafae531e8..3ca17ea6208b8fb93771c3d117568a2a3bcdd0cd 100644
--- a/components/net/sf/briar/transport/TagEncoder.java
+++ b/components/net/sf/briar/transport/TagEncoder.java
@@ -6,28 +6,22 @@ import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 import java.security.GeneralSecurityException;
 
 import javax.crypto.Cipher;
-import javax.crypto.spec.IvParameterSpec;
 
 import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.util.ByteUtils;
 
 class TagEncoder {
 
-	private static final byte[] BLANK = new byte[TAG_LENGTH];
-
-	static void encodeTag(byte[] tag, long frame, Cipher tagCipher,
+	static void encodeTag(byte[] tag, long segmentNumber, Cipher tagCipher,
 			ErasableKey tagKey) {
 		if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
-		if(frame < 0 || frame > MAX_32_BIT_UNSIGNED)
+		if(segmentNumber < 0 || segmentNumber > MAX_32_BIT_UNSIGNED)
 			throw new IllegalArgumentException();
-		// Encode the frame number as a uint32 at the end of the IV
-		byte[] iv = new byte[tagCipher.getBlockSize()];
-		if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
-		ByteUtils.writeUint32(frame, iv, iv.length - 4);
-		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		// Encode the segment number as a uint32 at the end of the tag
+		ByteUtils.writeUint32(segmentNumber, tag, TAG_LENGTH - 4);
 		try {
-			tagCipher.init(Cipher.ENCRYPT_MODE, tagKey, ivSpec);
-			int encrypted = tagCipher.doFinal(BLANK, 0, TAG_LENGTH, tag);
+			tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
+			int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
 			if(encrypted != TAG_LENGTH) throw new IllegalArgumentException();
 		} catch(GeneralSecurityException e) {
 			// Unsuitable cipher or key
@@ -35,26 +29,18 @@ class TagEncoder {
 		}
 	}
 
-	static boolean validateTag(byte[] tag, long frame, Cipher tagCipher,
-			ErasableKey tagKey) {
-		if(frame < 0 || frame > MAX_32_BIT_UNSIGNED)
-			throw new IllegalArgumentException();
-		if(tag.length < TAG_LENGTH) return false;
-		// Encode the frame number as a uint32 at the end of the IV
-		byte[] iv = new byte[tagCipher.getBlockSize()];
-		if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
-		ByteUtils.writeUint32(frame, iv, iv.length - 4);
-		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+	static long decodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey) {
+		if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
 		try {
-			tagCipher.init(Cipher.DECRYPT_MODE, tagKey, ivSpec);
+			tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
 			byte[] plaintext = tagCipher.doFinal(tag, 0, TAG_LENGTH);
 			if(plaintext.length != TAG_LENGTH)
 				throw new IllegalArgumentException();
-			// The plaintext should be blank
-			for(int i = 0; i < plaintext.length; i++) {
-				if(plaintext[i] != 0) return false;
+			// All but the last four bytes of the plaintext should be blank
+			for(int i = 0; i < TAG_LENGTH - 4; i++) {
+				if(plaintext[i] != 0) return -1;
 			}
-			return true;
+			return ByteUtils.readUint32(plaintext, TAG_LENGTH - 4);
 		} catch(GeneralSecurityException e) {
 			// Unsuitable cipher or key
 			throw new IllegalArgumentException(e);
diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java
index 2eda0dc36597ae0d3c8eec7c82ed0348844a506e..344561e799c53a367719f71391c7fefdd35937f5 100644
--- a/test/net/sf/briar/transport/ConnectionReaderImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionReaderImplTest.java
@@ -33,7 +33,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Read the frame
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		// There should be no bytes available before EOF
 		assertEquals(-1, r.getInputStream().read());
 	}
@@ -51,7 +53,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Read the frame
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		// There should be one byte available before EOF
 		assertEquals(0, r.getInputStream().read());
 		assertEquals(-1, r.getInputStream().read());
@@ -77,7 +81,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Read the first frame
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		byte[] read = new byte[MAX_PAYLOAD_LENGTH];
 		TestUtils.readFully(r.getInputStream(), read);
 		// Try to read the second frame
@@ -111,7 +117,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Read the first frame
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		byte[] read = new byte[MAX_PAYLOAD_LENGTH - paddingLength];
 		TestUtils.readFully(r.getInputStream(), read);
 		// Try to read the second frame
@@ -137,7 +145,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Read the frame
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		// The non-zero padding should be rejected
 		try {
 			r.getInputStream().read();
@@ -169,7 +179,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Read the frames
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		byte[] read = new byte[payloadLength];
 		TestUtils.readFully(r.getInputStream(), read);
 		assertArrayEquals(new byte[payloadLength], read);
@@ -193,7 +205,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Try to read the frame - not a single byte should be read
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		try {
 			r.getInputStream().read();
 			fail();
@@ -215,7 +229,9 @@ public class ConnectionReaderImplTest extends TransportTest {
 		// Try to read the frame - not a single byte should be read
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
 		IncomingEncryptionLayer decrypter = new NullIncomingEncryptionLayer(in);
-		ConnectionReader r = new ConnectionReaderImpl(decrypter, mac, macKey);
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader r = new ConnectionReaderImpl(correcter, mac, macKey);
 		try {
 			r.getInputStream().read();
 			fail();
diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
index a35279e54393359cb03e5085256811ec7033d561..a18f12b5be892295cb88813d2867c5d69e589337 100644
--- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
@@ -618,7 +618,7 @@ public class ConnectionRecogniserImplTest extends BriarTestCase {
 		ErasableKey tagKey = crypto.deriveTagKey(secret, true);
 		Cipher tagCipher = crypto.getTagCipher();
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
 		return tag;
 	}
 }
diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java
index 1646c8c36034d8dbb08e252d1b26a6e8d92cdcff..4699597f901a96b5aaff5fc151ed066853bdc078 100644
--- a/test/net/sf/briar/transport/FrameReadWriteTest.java
+++ b/test/net/sf/briar/transport/FrameReadWriteTest.java
@@ -62,7 +62,7 @@ public class FrameReadWriteTest extends BriarTestCase {
 	private void testWriteAndRead(boolean initiator) throws Exception {
 		// Encode the tag
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
 		// Generate two random frames
 		byte[] frame = new byte[12345];
 		random.nextBytes(frame);
@@ -89,11 +89,13 @@ public class FrameReadWriteTest extends BriarTestCase {
 		byte[] recoveredTag = new byte[TAG_LENGTH];
 		assertEquals(TAG_LENGTH, in.read(recoveredTag));
 		assertArrayEquals(tag, recoveredTag);
-		assertTrue(TagEncoder.validateTag(tag, 0, tagCipher, tagKey));
+		assertEquals(0L, TagEncoder.decodeTag(tag, tagCipher, tagKey));
 		// Read the frames back
 		IncomingEncryptionLayer decrypter = new IncomingEncryptionLayerImpl(in,
 				tagCipher, frameCipher, tagKey, frameKey, false);
-		ConnectionReader reader = new ConnectionReaderImpl(decrypter, mac,
+		IncomingErrorCorrectionLayer correcter =
+			new NullIncomingErrorCorrectionLayer(decrypter);
+		ConnectionReader reader = new ConnectionReaderImpl(correcter, mac,
 				macKey);
 		InputStream in1 = reader.getInputStream();
 		byte[] recovered = new byte[frame.length];
diff --git a/test/net/sf/briar/transport/IncomingEncryptionLayerImplTest.java b/test/net/sf/briar/transport/IncomingEncryptionLayerImplTest.java
index 62724afbb8c3e604c27365d5640de8cdb62ac140..4db4ed9297a28b9e957e464f3f4a0229db82d73e 100644
--- a/test/net/sf/briar/transport/IncomingEncryptionLayerImplTest.java
+++ b/test/net/sf/briar/transport/IncomingEncryptionLayerImplTest.java
@@ -2,7 +2,6 @@ package net.sf.briar.transport;
 
 import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
-import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 
 import java.io.ByteArrayInputStream;
@@ -13,6 +12,7 @@ import javax.crypto.spec.IvParameterSpec;
 import net.sf.briar.BriarTestCase;
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.Segment;
 import net.sf.briar.crypto.CryptoModule;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -38,14 +38,14 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
 
 	@Test
 	public void testDecryptionWithFirstSegmentTagged() throws Exception {
-		// Calculate the ciphertext for the first frame
+		// Calculate the ciphertext for the first segment
 		byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0);
 		byte[] iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize());
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext, 0, plaintext.length);
-		// Calculate the ciphertext for the second frame
+		// Calculate the ciphertext for the second segment
 		byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0);
 		IvEncoder.updateIv(iv, 1L);
@@ -62,13 +62,19 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
 		IncomingEncryptionLayer decrypter = new IncomingEncryptionLayerImpl(in,
 				tagCipher, frameCipher, tagKey, frameKey, false);
 		// First frame
-		byte[] decrypted = new byte[MAX_FRAME_LENGTH];
-		assertEquals(plaintext.length, decrypter.readFrame(decrypted));
+		Segment s = new SegmentImpl();
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext.length, s.getLength());
+		assertEquals(0L, s.getSegmentNumber());
+		byte[] decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext.length; i++) {
 			assertEquals(plaintext[i], decrypted[i]);
 		}
 		// Second frame
-		assertEquals(plaintext1.length, decrypter.readFrame(decrypted));
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext1.length, s.getLength());
+		assertEquals(1L, s.getSegmentNumber());
+		decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext1.length; i++) {
 			assertEquals(plaintext1[i], decrypted[i]);
 		}
@@ -76,18 +82,18 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
 
 	@Test
 	public void testDecryptionWithEverySegmentTagged() throws Exception {
-		// Calculate the ciphertext for the first frame
+		// Calculate the ciphertext for the first segment
 		byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0);
 		byte[] iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize());
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext, 0, plaintext.length);
-		// Calculate the ciphertext for the second frame, including its tag
+		// Calculate the ciphertext for the second segment, including its tag
 		byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0);
 		byte[] ciphertext1 = new byte[TAG_LENGTH + plaintext1.length];
-		TagEncoder.encodeTag(ciphertext1, 1, tagCipher, tagKey);
+		TagEncoder.encodeTag(ciphertext1, 1L, tagCipher, tagKey);
 		IvEncoder.updateIv(iv, 1L);
 		ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
@@ -102,13 +108,19 @@ public class IncomingEncryptionLayerImplTest extends BriarTestCase {
 		IncomingEncryptionLayer decrypter = new IncomingEncryptionLayerImpl(in,
 				tagCipher, frameCipher, tagKey, frameKey, true);
 		// First frame
-		byte[] decrypted = new byte[MAX_FRAME_LENGTH];
-		assertEquals(plaintext.length, decrypter.readFrame(decrypted));
+		Segment s = new SegmentImpl();
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext.length, s.getLength());
+		assertEquals(0L, s.getSegmentNumber());
+		byte[] decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext.length; i++) {
 			assertEquals(plaintext[i], decrypted[i]);
 		}
 		// Second frame
-		assertEquals(plaintext1.length, decrypter.readFrame(decrypted));
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext1.length, s.getLength());
+		assertEquals(1L, s.getSegmentNumber());
+		decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext1.length; i++) {
 			assertEquals(plaintext1[i], decrypted[i]);
 		}
diff --git a/test/net/sf/briar/transport/IncomingSegmentedEncryptionLayerTest.java b/test/net/sf/briar/transport/IncomingSegmentedEncryptionLayerTest.java
index f5c70be4d84074365a588f88588170fd366cf79c..fbcf8cc78c226dd2b7626e9dd7794a64830db845 100644
--- a/test/net/sf/briar/transport/IncomingSegmentedEncryptionLayerTest.java
+++ b/test/net/sf/briar/transport/IncomingSegmentedEncryptionLayerTest.java
@@ -2,7 +2,6 @@ package net.sf.briar.transport;
 
 import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
-import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 
 import java.io.IOException;
@@ -39,14 +38,14 @@ public class IncomingSegmentedEncryptionLayerTest extends BriarTestCase {
 
 	@Test
 	public void testDecryptionWithFirstSegmentTagged() throws Exception {
-		// Calculate the ciphertext for the first frame
+		// Calculate the ciphertext for the first segment
 		byte[] plaintext = new byte[FRAME_HEADER_LENGTH + 123 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext, 0L, 123, 0);
 		byte[] iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize());
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext, 0, plaintext.length);
-		// Calculate the ciphertext for the second frame
+		// Calculate the ciphertext for the second segment
 		byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0);
 		IvEncoder.updateIv(iv, 1L);
@@ -61,13 +60,19 @@ public class IncomingSegmentedEncryptionLayerTest extends BriarTestCase {
 			new IncomingSegmentedEncryptionLayer(in, tagCipher, frameCipher,
 					tagKey, frameKey, false);
 		// First frame
-		byte[] decrypted = new byte[MAX_FRAME_LENGTH];
-		assertEquals(plaintext.length, decrypter.readFrame(decrypted));
+		Segment s = new SegmentImpl();
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext.length, s.getLength());
+		assertEquals(0L, s.getSegmentNumber());
+		byte[] decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext.length; i++) {
 			assertEquals(plaintext[i], decrypted[i]);
 		}
 		// Second frame
-		assertEquals(plaintext1.length, decrypter.readFrame(decrypted));
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext1.length, s.getLength());
+		assertEquals(1L, s.getSegmentNumber());
+		decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext1.length; i++) {
 			assertEquals(plaintext1[i], decrypted[i]);
 		}
@@ -86,7 +91,7 @@ public class IncomingSegmentedEncryptionLayerTest extends BriarTestCase {
 		byte[] plaintext1 = new byte[FRAME_HEADER_LENGTH + 1234 + MAC_LENGTH];
 		HeaderEncoder.encodeHeader(plaintext1, 1L, 1234, 0);
 		byte[] ciphertext1 = new byte[TAG_LENGTH + plaintext1.length];
-		TagEncoder.encodeTag(ciphertext1, 1, tagCipher, tagKey);
+		TagEncoder.encodeTag(ciphertext1, 1L, tagCipher, tagKey);
 		IvEncoder.updateIv(iv, 1L);
 		ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
@@ -99,13 +104,19 @@ public class IncomingSegmentedEncryptionLayerTest extends BriarTestCase {
 			new IncomingSegmentedEncryptionLayer(in, tagCipher, frameCipher,
 					tagKey, frameKey, true);
 		// First frame
-		byte[] decrypted = new byte[MAX_FRAME_LENGTH];
-		assertEquals(plaintext.length, decrypter.readFrame(decrypted));
+		Segment s = new SegmentImpl();
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext.length, s.getLength());
+		assertEquals(0L, s.getSegmentNumber());
+		byte[] decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext.length; i++) {
 			assertEquals(plaintext[i], decrypted[i]);
 		}
 		// Second frame
-		assertEquals(plaintext1.length, decrypter.readFrame(decrypted));
+		assertTrue(decrypter.readSegment(s));
+		assertEquals(plaintext1.length, s.getLength());
+		assertEquals(1L, s.getSegmentNumber());
+		decrypted = s.getBuffer();
 		for(int i = 0; i < plaintext1.length; i++) {
 			assertEquals(plaintext1[i], decrypted[i]);
 		}
diff --git a/test/net/sf/briar/transport/NullIncomingEncryptionLayer.java b/test/net/sf/briar/transport/NullIncomingEncryptionLayer.java
index 03892bdaf6d00900e73b8c655e418805f1f1c3ee..057bcea77bb6753f5cbacb900eaefc58bf76cedd 100644
--- a/test/net/sf/briar/transport/NullIncomingEncryptionLayer.java
+++ b/test/net/sf/briar/transport/NullIncomingEncryptionLayer.java
@@ -9,38 +9,44 @@ import java.io.IOException;
 import java.io.InputStream;
 
 import net.sf.briar.api.FormatException;
+import net.sf.briar.api.plugins.Segment;
 
 /** An encryption layer that performs no encryption. */
 class NullIncomingEncryptionLayer implements IncomingEncryptionLayer {
 
 	private final InputStream in;
 
+	private long segmentNumber = 0L;
+
 	NullIncomingEncryptionLayer(InputStream in) {
 		this.in = in;
 	}
 
-	public int readFrame(byte[] b) throws IOException {
-		// Read the header to determine the frame length
+	public boolean readSegment(Segment s) throws IOException {
+		byte[] buf = s.getBuffer();
+		// Read the frame header
 		int offset = 0, length = FRAME_HEADER_LENGTH;
 		while(offset < length) {
-			int read = in.read(b, offset, length - offset);
+			int read = in.read(buf, offset, length - offset);
 			if(read == -1) {
-				if(offset == 0) return -1;
+				if(offset == 0) return false;
 				throw new EOFException();
 			}
 			offset += read;
 		}
-		// Parse the header
-		int payload = HeaderEncoder.getPayloadLength(b);
-		int padding = HeaderEncoder.getPaddingLength(b);
+		// Parse the frame header
+		int payload = HeaderEncoder.getPayloadLength(buf);
+		int padding = HeaderEncoder.getPaddingLength(buf);
 		length = FRAME_HEADER_LENGTH + payload + padding + MAC_LENGTH;
 		if(length > MAX_FRAME_LENGTH) throw new FormatException();
-		// Read the remainder of the frame
+		// Read the remainder of the frame/segment
 		while(offset < length) {
-			int read = in.read(b, offset, length - offset);
+			int read = in.read(buf, offset, length - offset);
 			if(read == -1) throw new EOFException();
 			offset += read;
 		}
-		return length;
+		s.setLength(length);
+		s.setSegmentNumber(segmentNumber++);
+		return true;
 	}
 }
diff --git a/test/net/sf/briar/transport/OutgoingEncryptionLayerImplTest.java b/test/net/sf/briar/transport/OutgoingEncryptionLayerImplTest.java
index 8aa93d68e8ddb87423717f4744eb8b4b16891d08..6e16d1aa7245e92424550d978b347e1b80c9b112 100644
--- a/test/net/sf/briar/transport/OutgoingEncryptionLayerImplTest.java
+++ b/test/net/sf/briar/transport/OutgoingEncryptionLayerImplTest.java
@@ -39,14 +39,14 @@ public class OutgoingEncryptionLayerImplTest extends BriarTestCase {
 	public void testEncryptionWithFirstSegmentTagged() throws Exception {
 		// Calculate the expected tag
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
-		// Calculate the expected ciphertext for the first frame
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
+		// Calculate the expected ciphertext for the first segment
 		byte[] iv = new byte[frameCipher.getBlockSize()];
 		byte[] plaintext = new byte[123 + MAC_LENGTH];
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext);
-		// Calculate the expected ciphertext for the second frame
+		// Calculate the expected ciphertext for the second segment
 		byte[] plaintext1 = new byte[1234 + MAC_LENGTH];
 		IvEncoder.updateIv(iv, 1L);
 		ivSpec = new IvParameterSpec(iv);
@@ -76,17 +76,17 @@ public class OutgoingEncryptionLayerImplTest extends BriarTestCase {
 	public void testEncryptionWithEverySegmentTagged() throws Exception {
 		// Calculate the expected tag for the first segment
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
-		// Calculate the expected ciphertext for the first frame
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
+		// Calculate the expected ciphertext for the first segment
 		byte[] iv = new byte[frameCipher.getBlockSize()];
 		byte[] plaintext = new byte[123 + MAC_LENGTH];
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext);
-		// Calculate the expected tag for the second frame
+		// Calculate the expected tag for the second segment
 		byte[] tag1 = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag1, 1, tagCipher, tagKey);
-		// Calculate the expected ciphertext for the second frame
+		TagEncoder.encodeTag(tag1, 1L, tagCipher, tagKey);
+		// Calculate the expected ciphertext for the second segment
 		byte[] plaintext1 = new byte[1234 + MAC_LENGTH];
 		IvEncoder.updateIv(iv, 1L);
 		ivSpec = new IvParameterSpec(iv);
diff --git a/test/net/sf/briar/transport/OutgoingSegmentedEncryptionLayerTest.java b/test/net/sf/briar/transport/OutgoingSegmentedEncryptionLayerTest.java
index ecbbbc1c68ed981bbdd92fea0af047d25330ed95..0fa64f2c0cc711c3ef8471ecd10eaaf7623354a3 100644
--- a/test/net/sf/briar/transport/OutgoingSegmentedEncryptionLayerTest.java
+++ b/test/net/sf/briar/transport/OutgoingSegmentedEncryptionLayerTest.java
@@ -42,14 +42,14 @@ public class OutgoingSegmentedEncryptionLayerTest extends BriarTestCase {
 	public void testEncryptionWithFirstSegmentTagged() throws Exception {
 		// Calculate the expected tag
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
-		// Calculate the expected ciphertext for the first frame
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
+		// Calculate the expected ciphertext for the first segment
 		byte[] iv = new byte[frameCipher.getBlockSize()];
 		byte[] plaintext = new byte[123 + MAC_LENGTH];
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext);
-		// Calculate the expected ciphertext for the second frame
+		// Calculate the expected ciphertext for the second segment
 		byte[] plaintext1 = new byte[1234 + MAC_LENGTH];
 		IvEncoder.updateIv(iv, 1L);
 		ivSpec = new IvParameterSpec(iv);
@@ -78,19 +78,19 @@ public class OutgoingSegmentedEncryptionLayerTest extends BriarTestCase {
 
 	@Test
 	public void testEncryptionWithEverySegmentTagged() throws Exception {
-		// Calculate the expected tag for the first frame
+		// Calculate the expected tag for the first segment
 		byte[] tag = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag, 0, tagCipher, tagKey);
-		// Calculate the expected ciphertext for the first frame
+		TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
+		// Calculate the expected ciphertext for the first segment
 		byte[] iv = new byte[frameCipher.getBlockSize()];
 		byte[] plaintext = new byte[123 + MAC_LENGTH];
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
 		frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
 		byte[] ciphertext = frameCipher.doFinal(plaintext);
-		// Calculate the expected tag for the second frame
+		// Calculate the expected tag for the second segment
 		byte[] tag1 = new byte[TAG_LENGTH];
-		TagEncoder.encodeTag(tag1, 1, tagCipher, tagKey);
-		// Calculate the expected ciphertext for the second frame
+		TagEncoder.encodeTag(tag1, 1L, tagCipher, tagKey);
+		// Calculate the expected ciphertext for the second segment
 		byte[] plaintext1 = new byte[1234 + MAC_LENGTH];
 		IvEncoder.updateIv(iv, 1L);
 		ivSpec = new IvParameterSpec(iv);
diff --git a/test/net/sf/briar/transport/TransportTest.java b/test/net/sf/briar/transport/TransportTest.java
index 3db6a202fdbbabc4fb144d1d0a9aad53d1c59ecd..c44e6fc2c76000ad8f8199168b9ea16e00f20f99 100644
--- a/test/net/sf/briar/transport/TransportTest.java
+++ b/test/net/sf/briar/transport/TransportTest.java
@@ -16,8 +16,8 @@ import com.google.inject.Injector;
 
 public abstract class TransportTest extends BriarTestCase {
 
-	static final int MAX_PAYLOAD_LENGTH
-	= MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - MAC_LENGTH;
+	static final int MAX_PAYLOAD_LENGTH =
+		MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - MAC_LENGTH;
 
 	protected final Mac mac;
 	protected final ErasableKey macKey;