diff --git a/api/net/sf/briar/api/plugins/FrameSink.java b/api/net/sf/briar/api/plugins/FrameSink.java
new file mode 100644
index 0000000000000000000000000000000000000000..841eb81592de6a0be9586492eb2f6b4d3c3bab5c
--- /dev/null
+++ b/api/net/sf/briar/api/plugins/FrameSink.java
@@ -0,0 +1,9 @@
+package net.sf.briar.api.plugins;
+
+import java.io.IOException;
+
+public interface FrameSink {
+
+	/** Writes the given frame. */
+	void writeFrame(byte[] b, int len) throws IOException;
+}
diff --git a/api/net/sf/briar/api/plugins/FrameSource.java b/api/net/sf/briar/api/plugins/FrameSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..59472821406614067106a27656e7170eb6beea85
--- /dev/null
+++ b/api/net/sf/briar/api/plugins/FrameSource.java
@@ -0,0 +1,12 @@
+package net.sf.briar.api.plugins;
+
+import java.io.IOException;
+
+public interface FrameSource {
+
+	/**
+	 * Reads a frame into the given buffer and returns its length, or -1 if no
+	 * more frames can be read.
+	 */
+	int readFrame(byte[] b) throws IOException;
+}
diff --git a/api/net/sf/briar/api/plugins/duplex/DuplexSegmentedTransportConnection.java b/api/net/sf/briar/api/plugins/duplex/DuplexSegmentedTransportConnection.java
index 3ff03462208d071c5309abc66144cf93605eacd3..3adad239866e3d75ad4c54838ec3284d12a2ba12 100644
--- a/api/net/sf/briar/api/plugins/duplex/DuplexSegmentedTransportConnection.java
+++ b/api/net/sf/briar/api/plugins/duplex/DuplexSegmentedTransportConnection.java
@@ -1,22 +1,15 @@
 package net.sf.briar.api.plugins.duplex;
 
-import java.io.IOException;
+import net.sf.briar.api.plugins.FrameSink;
+import net.sf.briar.api.plugins.FrameSource;
 
 /**
  * An interface for reading and writing data over a duplex segmented transport.
  * The connection is not responsible for encrypting/decrypting or authenticating
  * the data.
  */
-public interface DuplexSegmentedTransportConnection {
-
-	/**
-	 * Reads a frame into the given buffer and returns its length, or -1 if no
-	 * more frames can be read.
-	 */
-	int readFrame(byte[] b) throws IOException;
-
-	/** Writes the given frame to the transport. */
-	void writeFrame(byte[] b, int len) throws IOException;
+public interface DuplexSegmentedTransportConnection extends FrameSource,
+FrameSink {
 
 	/**
 	 * Returns true if the output stream should be flushed after each packet.
diff --git a/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportReader.java b/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportReader.java
index 0d9272d5584e5f76a7989a2ceaa2c97d2c343e86..66ab1a91c40234f26c49cd3148f5fde5cd81efe9 100644
--- a/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportReader.java
+++ b/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportReader.java
@@ -1,19 +1,13 @@
 package net.sf.briar.api.plugins.simplex;
 
-import java.io.IOException;
+import net.sf.briar.api.plugins.FrameSource;
 
 /**
  * An interface for reading data from a simplex segmented transport. The reader
  * is not responsible for decrypting or authenticating the data before
  * returning it.
  */
-public interface SimplexSegmentedTransportReader {
-
-	/**
-	 * Reads a frame into the given buffer and returns its length, or -1 if no
-	 * more frames can be read.
-	 */
-	int readFrame(byte[] b) throws IOException;
+public interface SimplexSegmentedTransportReader extends FrameSource {
 
 	/**
 	 * Closes the reader and disposes of any associated resources. The first
diff --git a/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportWriter.java b/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportWriter.java
index e6c09b3a3e8ba2b679955a36e59bf18bf9ce8f9b..ff909847eba576272a27e41e09c9709ed7ba6f70 100644
--- a/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportWriter.java
+++ b/api/net/sf/briar/api/plugins/simplex/SimplexSegmentedTransportWriter.java
@@ -1,7 +1,5 @@
 package net.sf.briar.api.plugins.simplex;
 
-import java.io.IOException;
-
 /**
  * An interface for writing data to a simplex segmented transport. The writer is
  * not responsible for authenticating or encrypting the data before writing it.
@@ -11,9 +9,6 @@ public interface SimplexSegmentedTransportWriter {
 	/** Returns the capacity of the transport in bytes. */
 	long getCapacity();
 
-	/** Writes the given frame to the transport. */
-	void writeFrame(byte[] b, int len) throws IOException;
-
 	/**
 	 * Returns true if the output stream should be flushed after each packet.
 	 */
diff --git a/components/net/sf/briar/transport/ConnectionDecrypter.java b/components/net/sf/briar/transport/ConnectionDecrypter.java
index e872f242fecccfdb61c60e8f3063f60ec6d88e40..c8facbdc74edef04b11c0f7dbc99dd53e308bdf9 100644
--- a/components/net/sf/briar/transport/ConnectionDecrypter.java
+++ b/components/net/sf/briar/transport/ConnectionDecrypter.java
@@ -1,13 +1,100 @@
 package net.sf.briar.transport;
 
+import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_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.EOFException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+
+import net.sf.briar.api.FormatException;
+import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.FrameSource;
+
+class ConnectionDecrypter implements FrameSource {
+
+	private final InputStream in;
+	private final Cipher frameCipher;
+	private final ErasableKey frameKey;
+	private final int macLength, blockSize;
+	private final byte[] iv;
+
+	private long frame = 0L;
 
-/** Decrypts unauthenticated data received over a connection. */
-interface ConnectionDecrypter {
+	ConnectionDecrypter(InputStream in, Cipher frameCipher,
+			ErasableKey frameKey, int macLength) {
+		this.in = in;
+		this.frameCipher = frameCipher;
+		this.frameKey = frameKey;
+		this.macLength = macLength;
+		blockSize = frameCipher.getBlockSize();
+		if(blockSize < FRAME_HEADER_LENGTH)
+			throw new IllegalArgumentException();
+		iv = IvEncoder.encodeIv(0, blockSize);
+	}
 
-	/**
-	 * Reads and decrypts a frame into the given buffer and returns the length
-	 * of the decrypted frame, or -1 if no more frames can be read.
-	 */
-	int readFrame(byte[] b) throws IOException;
-}
+	public int readFrame(byte[] b) throws IOException {
+		if(b.length < MAX_FRAME_LENGTH) throw new IllegalArgumentException();
+		if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
+		// Initialise the cipher
+		IvEncoder.updateIv(iv, frame);
+		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		try {
+			frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
+		} catch(GeneralSecurityException badIvOrKey) {
+			throw new RuntimeException(badIvOrKey);
+		}
+		try {
+			// Read the first block
+			int offset = 0;
+			while(offset < blockSize) {
+				int read = in.read(b, offset, blockSize - offset);
+				if(read == -1) {
+					if(offset == 0) return -1;
+					if(offset < blockSize) throw new EOFException();
+					break;
+				}
+				offset += read;
+			}
+			// Decrypt the first block
+			try {
+				int decrypted = frameCipher.update(b, 0, blockSize, b);
+				assert decrypted == blockSize;
+			} catch(GeneralSecurityException badCipher) {
+				throw new RuntimeException(badCipher);
+			}
+			// Validate and parse the header
+			int max = MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - macLength;
+			if(!HeaderEncoder.validateHeader(b, frame, max))
+				throw new FormatException();
+			int payload = HeaderEncoder.getPayloadLength(b);
+			int padding = HeaderEncoder.getPaddingLength(b);
+			int length = FRAME_HEADER_LENGTH + payload + padding + macLength;
+			if(length > MAX_FRAME_LENGTH) throw new FormatException();
+			// Read the remainder of the frame
+			while(offset < length) {
+				int read = in.read(b, offset, length - offset);
+				if(read == -1) throw new EOFException();
+				offset += read;
+			}
+			// Decrypt the remainder of the frame
+			try {
+				int decrypted = frameCipher.doFinal(b, blockSize,
+						length - blockSize, b, blockSize);
+				assert decrypted == length - blockSize;
+			} catch(GeneralSecurityException badCipher) {
+				throw new RuntimeException(badCipher);
+			}
+			frame++;
+			return length;
+		} catch(IOException e) {
+			frameKey.erase();
+			throw e;
+		}
+	}
+}
\ No newline at end of file
diff --git a/components/net/sf/briar/transport/ConnectionEncrypter.java b/components/net/sf/briar/transport/ConnectionEncrypter.java
index 11cf16e7c54c413448ca854ad7c1547a1cbc1141..930f1ee81988065ae27a53a6774664738bc7a853 100644
--- a/components/net/sf/briar/transport/ConnectionEncrypter.java
+++ b/components/net/sf/briar/transport/ConnectionEncrypter.java
@@ -2,11 +2,10 @@ package net.sf.briar.transport;
 
 import java.io.IOException;
 
-/** Encrypts authenticated data to be sent over a connection. */
-interface ConnectionEncrypter {
+import net.sf.briar.api.plugins.FrameSink;
 
-	/** Encrypts and writes the given frame. */
-	void writeFrame(byte[] b, int len) throws IOException;
+/** Encrypts authenticated data to be sent over a connection. */
+interface ConnectionEncrypter extends FrameSink {
 
 	/** Flushes the output stream. */
 	void flush() throws IOException;
diff --git a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
index e7e161652414c6d76b2c5cddf4ed159bc569bc68..9370c20f9a1095f4ed2ae24dd583031e08eafdb7 100644
--- a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
+++ b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
@@ -1,6 +1,5 @@
 package net.sf.briar.transport;
 
-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;
@@ -32,33 +31,37 @@ class ConnectionEncrypterImpl implements ConnectionEncrypter {
 		// Encrypt the tag
 		tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
 		tagKey.erase();
-		if(tag.length != TAG_LENGTH) throw new IllegalArgumentException();
 	}
 
 	public void writeFrame(byte[] b, int len) throws IOException {
-		try {
-			if(!tagWritten) {
-				out.write(tag);
-				capacity -= tag.length;
-				tagWritten = true;
-			}
-			if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
-			IvEncoder.updateIv(iv, frame);
-			IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
+		if(!tagWritten) {
 			try {
-				frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
-				int encrypted = frameCipher.doFinal(b, 0, len, b, 0);
-				assert encrypted == len;
-			} catch(GeneralSecurityException badCipher) {
-				throw new RuntimeException(badCipher);
+				out.write(tag);
+			} catch(IOException e) {
+				frameKey.erase();
+				throw e;
 			}
+			capacity -= tag.length;
+			tagWritten = true;
+		}
+		IvEncoder.updateIv(iv, frame);
+		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		try {
+			frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
+			int encrypted = frameCipher.doFinal(b, 0, len, b);
+			assert encrypted == len;
+		} catch(GeneralSecurityException badCipher) {
+			throw new RuntimeException(badCipher);
+		}
+		try {
 			out.write(b, 0, len);
-			capacity -= len;
-			frame++;
 		} catch(IOException e) {
 			frameKey.erase();
 			throw e;
 		}
+		capacity -= len;
+		frame++;
 	}
 
 	public void flush() throws IOException {
diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
index a80769c984d9a7ad8edd78f20b3815fa4b6fb2cd..91c092932004664608ea55ce925e2b428f93f028 100644
--- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
@@ -7,6 +7,7 @@ import javax.crypto.Mac;
 
 import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.FrameSource;
 import net.sf.briar.api.transport.ConnectionReader;
 import net.sf.briar.api.transport.ConnectionReaderFactory;
 import net.sf.briar.util.ByteUtils;
@@ -47,7 +48,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 		// Create the decrypter
 		Cipher frameCipher = crypto.getFrameCipher();
 		Mac mac = crypto.getMac();
-		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in,
+		FrameSource decrypter = new ConnectionDecrypter(in,
 				frameCipher, frameKey, mac.getMacLength());
 		// Create the reader
 		return new ConnectionReaderImpl(decrypter, mac, macKey);
diff --git a/components/net/sf/briar/transport/ConnectionReaderImpl.java b/components/net/sf/briar/transport/ConnectionReaderImpl.java
index 4aef95b3fc909c6922e8bb94d919a39360b023a2..49928cd4e7ff41421c7d81987fcc1714b850f2c4 100644
--- a/components/net/sf/briar/transport/ConnectionReaderImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderImpl.java
@@ -12,11 +12,12 @@ import javax.crypto.Mac;
 
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.FrameSource;
 import net.sf.briar.api.transport.ConnectionReader;
 
 class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 
-	private final ConnectionDecrypter decrypter;
+	private final FrameSource decrypter;
 	private final Mac mac;
 	private final int macLength;
 	private final byte[] buf;
@@ -24,8 +25,7 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
 	private long frame = 0L;
 	private int bufOffset = 0, bufLength = 0;
 
-	ConnectionReaderImpl(ConnectionDecrypter decrypter, Mac mac,
-			ErasableKey macKey) {
+	ConnectionReaderImpl(FrameSource decrypter, Mac mac, ErasableKey macKey) {
 		this.decrypter = decrypter;
 		this.mac = mac;
 		// Initialise the MAC
diff --git a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java b/components/net/sf/briar/transport/SegmentedConnectionDecrypter.java
similarity index 63%
rename from components/net/sf/briar/transport/ConnectionDecrypterImpl.java
rename to components/net/sf/briar/transport/SegmentedConnectionDecrypter.java
index b4d93232cefa587b673ad7302a2fd76057b53c2d..54d285d5ea2667d77497bd2f849a696343e5172e 100644
--- a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java
+++ b/components/net/sf/briar/transport/SegmentedConnectionDecrypter.java
@@ -4,9 +4,7 @@ import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_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.EOFException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.security.GeneralSecurityException;
 
 import javax.crypto.Cipher;
@@ -14,10 +12,11 @@ import javax.crypto.spec.IvParameterSpec;
 
 import net.sf.briar.api.FormatException;
 import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.FrameSource;
 
-class ConnectionDecrypterImpl implements ConnectionDecrypter {
+class SegmentedConnectionDecrypter implements FrameSource {
 
-	private final InputStream in;
+	private final FrameSource in;
 	private final Cipher frameCipher;
 	private final ErasableKey frameKey;
 	private final int macLength, blockSize;
@@ -25,7 +24,7 @@ class ConnectionDecrypterImpl implements ConnectionDecrypter {
 
 	private long frame = 0L;
 
-	ConnectionDecrypterImpl(InputStream in, Cipher frameCipher,
+	SegmentedConnectionDecrypter(FrameSource in, Cipher frameCipher,
 			ErasableKey frameKey, int macLength) {
 		this.in = in;
 		this.frameCipher = frameCipher;
@@ -49,21 +48,16 @@ class ConnectionDecrypterImpl implements ConnectionDecrypter {
 			throw new RuntimeException(badIvOrKey);
 		}
 		try {
-			// Read the first block
-			int offset = 0;
-			while(offset < blockSize) {
-				int read = in.read(b, offset, blockSize - offset);
-				if(read == -1) {
-					if(offset == 0) return -1;
-					if(offset < blockSize) throw new EOFException();
-					break;
-				}
-				offset += read;
-			}
-			// Decrypt the first block
+			// Read the frame
+			int length = in.readFrame(b);
+			if(length == -1) return -1;
+			if(length > MAX_FRAME_LENGTH) throw new FormatException();
+			if(length < FRAME_HEADER_LENGTH + macLength)
+				throw new FormatException();
+			// Decrypt the frame
 			try {
-				int decrypted = frameCipher.update(b, 0, blockSize, b);
-				assert decrypted == blockSize;
+				int decrypted = frameCipher.update(b, 0, length, b);
+				assert decrypted == length;
 			} catch(GeneralSecurityException badCipher) {
 				throw new RuntimeException(badCipher);
 			}
@@ -73,22 +67,8 @@ class ConnectionDecrypterImpl implements ConnectionDecrypter {
 				throw new FormatException();
 			int payload = HeaderEncoder.getPayloadLength(b);
 			int padding = HeaderEncoder.getPaddingLength(b);
-			int length = FRAME_HEADER_LENGTH + payload + padding + macLength;
-			if(length > MAX_FRAME_LENGTH) throw new FormatException();
-			// Read the remainder of the frame
-			while(offset < length) {
-				int read = in.read(b, offset, length - offset);
-				if(read == -1) throw new EOFException();
-				offset += read;
-			}
-			// Decrypt the remainder of the frame
-			try {
-				int decrypted = frameCipher.doFinal(b, blockSize,
-						length - blockSize, b, blockSize);
-				assert decrypted == length - blockSize;
-			} catch(GeneralSecurityException badCipher) {
-				throw new RuntimeException(badCipher);
-			}
+			if(length != FRAME_HEADER_LENGTH + payload + padding + macLength)
+				throw new FormatException();
 			frame++;
 			return length;
 		} catch(IOException e) {
@@ -96,4 +76,4 @@ class ConnectionDecrypterImpl implements ConnectionDecrypter {
 			throw e;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java b/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f67d9b9d91be969bdfa89fc34ae8ce670477d95c
--- /dev/null
+++ b/components/net/sf/briar/transport/SegmentedConnectionEncrypter.java
@@ -0,0 +1,72 @@
+package net.sf.briar.transport;
+
+import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+
+import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.plugins.FrameSink;
+
+class SegmentedConnectionEncrypter implements ConnectionEncrypter {
+
+	private final FrameSink out;
+	private final Cipher frameCipher;
+	private final ErasableKey frameKey;
+	private final byte[] iv, tag;
+
+	private long capacity, frame = 0L;
+	private boolean tagWritten = false;
+
+	SegmentedConnectionEncrypter(FrameSink out, long capacity, Cipher tagCipher,
+			Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey) {
+		this.out = out;
+		this.capacity = capacity;
+		this.frameCipher = frameCipher;
+		this.frameKey = frameKey;
+		iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize());
+		// Encrypt the tag
+		tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
+		tagKey.erase();
+	}
+
+	public void writeFrame(byte[] b, int len) throws IOException {
+		if(frame > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
+		int offset = 0;
+		if(!tagWritten) {
+			if(tag.length + len > b.length)
+				throw new IllegalArgumentException();
+			System.arraycopy(b, 0, b, tag.length, len);
+			System.arraycopy(tag, 0, b, 0, tag.length);
+			capacity -= tag.length;
+			tagWritten = true;
+			offset = tag.length;
+		}
+		IvEncoder.updateIv(iv, frame);
+		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		try {
+			frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
+			int encrypted = frameCipher.doFinal(b, offset, len, b, offset);
+			assert encrypted == len;
+		} catch(GeneralSecurityException badCipher) {
+			throw new RuntimeException(badCipher);
+		}
+		try {
+			out.writeFrame(b, offset + len);
+		} catch(IOException e) {
+			frameKey.erase();
+			throw e;
+		}
+		capacity -= len;
+		frame++;
+	}
+
+	public void flush() throws IOException {}
+
+	public long getRemainingCapacity() {
+		return capacity;
+	}
+}
\ No newline at end of file
diff --git a/test/net/sf/briar/crypto/CounterModeTest.java b/test/net/sf/briar/crypto/CounterModeTest.java
index d9f7131b672b8aa461975ad40aaf09a6bf47a8e9..07475d7d2a9bca0b4eb47c094b7b7b7b1b1d9707 100644
--- a/test/net/sf/briar/crypto/CounterModeTest.java
+++ b/test/net/sf/briar/crypto/CounterModeTest.java
@@ -55,7 +55,7 @@ public class CounterModeTest extends BriarTestCase {
 			cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 			byte[] ciphertext =
 				new byte[cipher.getOutputSize(plaintext.length)];
-			cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
+			cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
 			ciphertexts.add(new Bytes(ciphertext));
 		}
 		// All the ciphertexts should be distinct using Arrays.equals()
@@ -78,7 +78,7 @@ public class CounterModeTest extends BriarTestCase {
 			cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 			byte[] ciphertext =
 				new byte[cipher.getOutputSize(plaintext.length)];
-			cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
+			cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
 			ciphertexts.add(new Bytes(ciphertext));
 		}
 		assertEquals(1, ciphertexts.size());
@@ -98,7 +98,7 @@ public class CounterModeTest extends BriarTestCase {
 		Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
 		cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 		byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
-		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
+		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
 		// Make sure the IV array hasn't been modified
 		assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 2]);
 		assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 1]);
@@ -109,7 +109,7 @@ public class CounterModeTest extends BriarTestCase {
 		cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
 		cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 		byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)];
-		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1, 0);
+		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1);
 		// The last nine blocks of the first ciphertext should be identical to
 		// the first nine blocks of the second ciphertext
 		for(int i = 0; i < BLOCK_SIZE_BYTES * 9; i++) {
@@ -132,7 +132,7 @@ public class CounterModeTest extends BriarTestCase {
 		Cipher cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
 		cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 		byte[] ciphertext = new byte[cipher.getOutputSize(plaintext.length)];
-		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
+		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext);
 		// Make sure the IV array hasn't been modified
 		assertEquals(0, ivBytes[BLOCK_SIZE_BYTES - 3]);
 		assertEquals((byte) 255, ivBytes[BLOCK_SIZE_BYTES - 2]);
@@ -146,7 +146,7 @@ public class CounterModeTest extends BriarTestCase {
 		cipher = Cipher.getInstance(CIPHER_MODE, PROVIDER);
 		cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 		byte[] ciphertext1 = new byte[cipher.getOutputSize(plaintext.length)];
-		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1, 0);
+		cipher.doFinal(plaintext, 0, plaintext.length, ciphertext1);
 		// The last nine blocks of the first ciphertext should be identical to
 		// the first nine blocks of the second ciphertext
 		for(int i = 0; i < BLOCK_SIZE_BYTES * 9; i++) {
diff --git a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java
index e02447267a6d46a23ccad940d6eb7f051bbaf29a..c721d8b59b7150feb0b5a91813f2e30b39b16a4c 100644
--- a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java
@@ -11,6 +11,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.FrameSource;
 import net.sf.briar.crypto.CryptoModule;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -67,16 +68,16 @@ public class ConnectionDecrypterImplTest extends BriarTestCase {
 		out.write(ciphertext1);
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
 		// Use a ConnectionDecrypter to decrypt the ciphertext
-		ConnectionDecrypter d = new ConnectionDecrypterImpl(in, frameCipher,
+		FrameSource decrypter = new ConnectionDecrypter(in, frameCipher,
 				frameKey, MAC_LENGTH);
 		// First frame
 		byte[] decrypted = new byte[MAX_FRAME_LENGTH];
-		assertEquals(plaintext.length, d.readFrame(decrypted));
+		assertEquals(plaintext.length, decrypter.readFrame(decrypted));
 		for(int i = 0; i < plaintext.length; i++) {
 			assertEquals(plaintext[i], decrypted[i]);
 		}
 		// Second frame
-		assertEquals(plaintext1.length, d.readFrame(decrypted));
+		assertEquals(plaintext1.length, decrypter.readFrame(decrypted));
 		for(int i = 0; i < plaintext1.length; i++) {
 			assertEquals(plaintext1[i], decrypted[i]);
 		}
diff --git a/test/net/sf/briar/transport/ConnectionReaderImplTest.java b/test/net/sf/briar/transport/ConnectionReaderImplTest.java
index 0711f4f21727851ec74c514f511828df209097db..7a6f8000ad130c2e263122195ccd03645e4a1af5 100644
--- a/test/net/sf/briar/transport/ConnectionReaderImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionReaderImplTest.java
@@ -8,6 +8,7 @@ import java.io.ByteArrayInputStream;
 
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.FormatException;
+import net.sf.briar.api.plugins.FrameSource;
 import net.sf.briar.api.transport.ConnectionReader;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -31,7 +32,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
 		// Read the frame
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		// There should be no bytes available before EOF
 		assertEquals(-1, r.getInputStream().read());
@@ -49,7 +50,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength);
 		// Read the frame
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		// There should be one byte available before EOF
 		assertEquals(0, r.getInputStream().read());
@@ -75,7 +76,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		out.write(frame1);
 		// Read the first frame
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		byte[] read = new byte[maxPayloadLength];
 		TestUtils.readFully(r.getInputStream(), read);
@@ -109,7 +110,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		out.write(frame1);
 		// Read the first frame
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		byte[] read = new byte[maxPayloadLength - paddingLength];
 		TestUtils.readFully(r.getInputStream(), read);
@@ -135,7 +136,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		mac.doFinal(frame, FRAME_HEADER_LENGTH + payloadLength + paddingLength);
 		// Read the frame
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		// The non-zero padding should be rejected
 		try {
@@ -167,7 +168,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		out.write(frame1);
 		// Read the frames
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		byte[] read = new byte[payloadLength];
 		TestUtils.readFully(r.getInputStream(), read);
@@ -191,7 +192,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		frame[12] ^= 1;
 		// Try to read the frame - not a single byte should be read
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		try {
 			r.getInputStream().read();
@@ -213,7 +214,7 @@ public class ConnectionReaderImplTest extends TransportTest {
 		frame[17] ^= 1;
 		// Try to read the frame - not a single byte should be read
 		ByteArrayInputStream in = new ByteArrayInputStream(frame);
-		ConnectionDecrypter d = new NullConnectionDecrypter(in, macLength);
+		FrameSource d = new NullConnectionDecrypter(in, macLength);
 		ConnectionReader r = new ConnectionReaderImpl(d, mac, macKey);
 		try {
 			r.getInputStream().read();
diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java
index b59b3dd144db60420d31bef8b42f11b6fddbd7fb..ac51080048affe9549f54043f830b99d7e19e367 100644
--- a/test/net/sf/briar/transport/FrameReadWriteTest.java
+++ b/test/net/sf/briar/transport/FrameReadWriteTest.java
@@ -15,6 +15,7 @@ import javax.crypto.Mac;
 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.FrameSource;
 import net.sf.briar.api.transport.ConnectionReader;
 import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.crypto.CryptoModule;
@@ -89,8 +90,8 @@ public class FrameReadWriteTest extends BriarTestCase {
 		assertArrayEquals(tag, recoveredTag);
 		assertTrue(TagEncoder.validateTag(tag, 0, tagCipher, tagKey));
 		// Read the frames back
-		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in,
-				frameCipher, frameKey, mac.getMacLength());
+		FrameSource decrypter = new ConnectionDecrypter(in, frameCipher,
+				frameKey, mac.getMacLength());
 		ConnectionReader reader = new ConnectionReaderImpl(decrypter, mac,
 				macKey);
 		InputStream in1 = reader.getInputStream();
diff --git a/test/net/sf/briar/transport/NullConnectionDecrypter.java b/test/net/sf/briar/transport/NullConnectionDecrypter.java
index 68d378469e437127dcf4ed602eebdc4c83408e4e..319df394cc3f4f4988c6c1843734a46c717f4cdf 100644
--- a/test/net/sf/briar/transport/NullConnectionDecrypter.java
+++ b/test/net/sf/briar/transport/NullConnectionDecrypter.java
@@ -8,9 +8,10 @@ import java.io.IOException;
 import java.io.InputStream;
 
 import net.sf.briar.api.FormatException;
+import net.sf.briar.api.plugins.FrameSource;
 
-/** A ConnectionDecrypter that performs no decryption. */
-class NullConnectionDecrypter implements ConnectionDecrypter {
+/** A connection decrypter that performs no decryption. */
+class NullConnectionDecrypter implements FrameSource {
 
 	private final InputStream in;
 	private final int macLength;