From 10c3b21726821738006f8d2f087b3abb05f9ad22 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Wed, 21 Sep 2011 15:22:25 +0100
Subject: [PATCH] Expose the encryption and authentication overhead without
 breaking encapsulation.

This should allow callers to calculate maximum packet sizes without
knowing the details of the transport protocol.
---
 .../briar/api/protocol/ProtocolConstants.java | 12 ++++----
 .../briar/api/transport/ConnectionWriter.java |  7 +++++
 .../api/transport/TransportConstants.java     |  9 +++++-
 .../briar/transport/ConnectionEncrypter.java  |  7 +++++
 .../transport/ConnectionEncrypterImpl.java    |  5 ++++
 .../briar/transport/ConnectionWriterImpl.java | 24 ++++++++++++----
 .../transport/PaddedConnectionWriter.java     | 26 ++---------------
 .../transport/ConnectionWriterImplTest.java   | 28 +++++++++++++++++++
 .../transport/NullConnectionEncrypter.java    |  4 +++
 .../transport/PaddedConnectionWriterTest.java | 28 +++++++++++++++++++
 10 files changed, 115 insertions(+), 35 deletions(-)

diff --git a/api/net/sf/briar/api/protocol/ProtocolConstants.java b/api/net/sf/briar/api/protocol/ProtocolConstants.java
index 0a213a3561..a84f20e572 100644
--- a/api/net/sf/briar/api/protocol/ProtocolConstants.java
+++ b/api/net/sf/briar/api/protocol/ProtocolConstants.java
@@ -1,12 +1,14 @@
 package net.sf.briar.api.protocol;
 
+import net.sf.briar.api.transport.TransportConstants;
+
 public interface ProtocolConstants {
 
 	/**
-	 * The maximum length of a serialised packet in bytes. Since the protocol
-	 * does not aim for low latency, the two main constraints here are the
-	 * amount of memory used for parsing packets and the granularity of the
-	 * database transactions for generating and receiving packets.
+	 * The maximum length of a serialised packet in bytes. To allow for future
+	 * changes in the protocol, this is smaller than the minimum connection
+	 * length minus the encryption and authentication overhead.
 	 */
-	static final int MAX_PACKET_LENGTH = 1024 * 1024; // 1 MiB
+	static final int MAX_PACKET_LENGTH =
+		TransportConstants.MIN_CONNECTION_LENGTH - 1024;
 }
diff --git a/api/net/sf/briar/api/transport/ConnectionWriter.java b/api/net/sf/briar/api/transport/ConnectionWriter.java
index c7ed2038ac..e931cf7444 100644
--- a/api/net/sf/briar/api/transport/ConnectionWriter.java
+++ b/api/net/sf/briar/api/transport/ConnectionWriter.java
@@ -10,4 +10,11 @@ public interface ConnectionWriter {
 	 * be written.
 	 */
 	OutputStream getOutputStream();
+
+	/**
+	 * Returns the number of encrypted and authenticated bytes that can be
+	 * written without writing more than the given number of bytes, including
+	 * encryption and authentication overhead.
+	 */
+	long getCapacity(long capacity);
 }
diff --git a/api/net/sf/briar/api/transport/TransportConstants.java b/api/net/sf/briar/api/transport/TransportConstants.java
index 0aa0c01afa..7be2a44391 100644
--- a/api/net/sf/briar/api/transport/TransportConstants.java
+++ b/api/net/sf/briar/api/transport/TransportConstants.java
@@ -5,11 +5,18 @@ public interface TransportConstants {
 	/**
 	 * The maximum length of a frame in bytes, including the header and footer.
 	 */
-	static final int MAX_FRAME_LENGTH = 65536; // 2^16
+	static final int MAX_FRAME_LENGTH = 65536; // 2^16, 64 KiB
 
 	/**
 	 * The length in bytes of the encrypted IV that uniquely identifies a
 	 * connection.
 	 */
 	static final int IV_LENGTH = 16;
+
+	/**
+	 * The minimum connection length in bytes that all transport plugins must
+	 * support. Connections may be shorter than this length, but all transport
+	 * plugins must support connections of at least this length.
+	 */
+	static final int MIN_CONNECTION_LENGTH = 1024 * 1024; // 2^20, 1 MiB
 }
diff --git a/components/net/sf/briar/transport/ConnectionEncrypter.java b/components/net/sf/briar/transport/ConnectionEncrypter.java
index 595307547b..bf272997bc 100644
--- a/components/net/sf/briar/transport/ConnectionEncrypter.java
+++ b/components/net/sf/briar/transport/ConnectionEncrypter.java
@@ -11,4 +11,11 @@ interface ConnectionEncrypter {
 
 	/** Encrypts and writes the MAC for the current frame. */
 	void writeMac(byte[] mac) throws IOException;
+
+	/**
+	 * Returns the number of encrypted bytes that can be written without
+	 * writing more than the given number of bytes, including encryption
+	 * overhead.
+	 */
+	long getCapacity(long capacity);
 }
diff --git a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
index c505deda33..419445dcbd 100644
--- a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
+++ b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
@@ -58,6 +58,11 @@ implements ConnectionEncrypter {
 		betweenFrames = true;
 	}
 
+	public long getCapacity(long capacity) {
+		if(capacity < 0L) throw new IllegalArgumentException();
+		return ivWritten ? capacity : Math.max(0L, capacity - IV_LENGTH);
+	}
+
 	@Override
 	public void write(int b) throws IOException {
 		if(!ivWritten) writeIv();
diff --git a/components/net/sf/briar/transport/ConnectionWriterImpl.java b/components/net/sf/briar/transport/ConnectionWriterImpl.java
index d37f6ec200..61d6364471 100644
--- a/components/net/sf/briar/transport/ConnectionWriterImpl.java
+++ b/components/net/sf/briar/transport/ConnectionWriterImpl.java
@@ -20,13 +20,13 @@ import net.sf.briar.util.ByteUtils;
 class ConnectionWriterImpl extends FilterOutputStream
 implements ConnectionWriter {
 
-	private final ConnectionEncrypter encrypter;
-	private final Mac mac;
-	private final int maxPayloadLength;
-	private final ByteArrayOutputStream buf;
-	private final byte[] header;
+	protected final ConnectionEncrypter encrypter;
+	protected final Mac mac;
+	protected final int maxPayloadLength;
+	protected final ByteArrayOutputStream buf;
+	protected final byte[] header;
 
-	private long frame = 0L;
+	protected long frame = 0L;
 
 	ConnectionWriterImpl(ConnectionEncrypter encrypter, Mac mac) {
 		super(encrypter.getOutputStream());
@@ -41,6 +41,18 @@ implements ConnectionWriter {
 		return this;
 	}
 
+	public long getCapacity(long capacity) {
+		if(capacity < 0L) throw new IllegalArgumentException();
+		// Subtract the encryption overhead
+		capacity = encrypter.getCapacity(capacity);
+		// If there's any data buffered, subtract it and its auth overhead
+		int overheadPerFrame = header.length + mac.getMacLength();
+		if(buf.size() > 0) capacity -= buf.size() + overheadPerFrame;
+		// Subtract the auth overhead from the remaining capacity
+		long frames = (long) Math.ceil((double) capacity / MAX_FRAME_LENGTH);
+		return Math.max(0L, capacity - frames * overheadPerFrame);
+	}
+
 	@Override
 	public void flush() throws IOException {
 		if(buf.size() > 0) writeFrame();
diff --git a/components/net/sf/briar/transport/PaddedConnectionWriter.java b/components/net/sf/briar/transport/PaddedConnectionWriter.java
index 60eea4fa5f..ebab1f3eae 100644
--- a/components/net/sf/briar/transport/PaddedConnectionWriter.java
+++ b/components/net/sf/briar/transport/PaddedConnectionWriter.java
@@ -1,16 +1,11 @@
 package net.sf.briar.transport;
 
-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.ByteArrayOutputStream;
-import java.io.FilterOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 
 import javax.crypto.Mac;
 
-import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.util.ByteUtils;
 
 /**
@@ -19,33 +14,18 @@ import net.sf.briar.util.ByteUtils;
  * padding inserted if necessary. Calls to the writer's write() methods will
  * block until there is space to buffer the data.
  */
-class PaddedConnectionWriter extends FilterOutputStream
-implements ConnectionWriter {
+class PaddedConnectionWriter extends ConnectionWriterImpl {
 
-	private final ConnectionEncrypter encrypter;
-	private final Mac mac;
-	private final int maxPayloadLength;
-	private final ByteArrayOutputStream buf;
-	private final byte[] header, padding;
+	private final byte[] padding;
 
-	private long frame = 0L;
 	private boolean closed = false;
 	private IOException exception = null;
 
 	PaddedConnectionWriter(ConnectionEncrypter encrypter, Mac mac) {
-		super(encrypter.getOutputStream());
-		this.encrypter = encrypter;
-		this.mac = mac;
-		maxPayloadLength = MAX_FRAME_LENGTH - 4 - mac.getMacLength();
-		buf = new ByteArrayOutputStream(maxPayloadLength);
-		header = new byte[4];
+		super(encrypter, mac);
 		padding = new byte[maxPayloadLength];
 	}
 
-	public OutputStream getOutputStream() {
-		return this;
-	}
-
 	@Override
 	public synchronized void close() throws IOException {
 		if(exception != null) throw exception;
diff --git a/test/net/sf/briar/transport/ConnectionWriterImplTest.java b/test/net/sf/briar/transport/ConnectionWriterImplTest.java
index 985c962b0f..56528e9e24 100644
--- a/test/net/sf/briar/transport/ConnectionWriterImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionWriterImplTest.java
@@ -100,4 +100,32 @@ public class ConnectionWriterImplTest extends TransportTest {
 		byte[] actual = out.toByteArray();
 		assertTrue(Arrays.equals(expected, actual));
 	}
+
+	@Test
+	public void testGetCapacity() throws Exception {
+		int overheadPerFrame = 4 + mac.getMacLength();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		ConnectionEncrypter e = new NullConnectionEncrypter(out);
+		ConnectionWriterImpl w = new ConnectionWriterImpl(e, mac);
+		// Full frame
+		long capacity = w.getCapacity(MAX_FRAME_LENGTH);
+		assertEquals(MAX_FRAME_LENGTH - overheadPerFrame, capacity);
+		// Partial frame
+		capacity = w.getCapacity(overheadPerFrame + 1);
+		assertEquals(1, capacity);
+		// Full frame and partial frame
+		capacity = w.getCapacity(MAX_FRAME_LENGTH + 1);
+		assertEquals(MAX_FRAME_LENGTH + 1 - 2 * overheadPerFrame, capacity);
+		// Buffer some output
+		w.getOutputStream().write(0);
+		// Full frame minus buffered frame
+		capacity = w.getCapacity(MAX_FRAME_LENGTH);
+		assertEquals(MAX_FRAME_LENGTH - 1 - 2 * overheadPerFrame, capacity);
+		// Flush the buffer
+		w.flush();
+		assertEquals(1 + overheadPerFrame, out.size());
+		// Back to square one
+		capacity = w.getCapacity(MAX_FRAME_LENGTH);
+		assertEquals(MAX_FRAME_LENGTH - overheadPerFrame, capacity);
+	}
 }
diff --git a/test/net/sf/briar/transport/NullConnectionEncrypter.java b/test/net/sf/briar/transport/NullConnectionEncrypter.java
index c9f492d69b..7ba7b9d7a5 100644
--- a/test/net/sf/briar/transport/NullConnectionEncrypter.java
+++ b/test/net/sf/briar/transport/NullConnectionEncrypter.java
@@ -19,4 +19,8 @@ class NullConnectionEncrypter implements ConnectionEncrypter {
 	public void writeMac(byte[] mac) throws IOException {
 		out.write(mac);
 	}
+
+	public long getCapacity(long capacity) {
+		return capacity;
+	}
 }
diff --git a/test/net/sf/briar/transport/PaddedConnectionWriterTest.java b/test/net/sf/briar/transport/PaddedConnectionWriterTest.java
index 705e76a48d..8cdbb78f4d 100644
--- a/test/net/sf/briar/transport/PaddedConnectionWriterTest.java
+++ b/test/net/sf/briar/transport/PaddedConnectionWriterTest.java
@@ -160,4 +160,32 @@ public class PaddedConnectionWriterTest extends TransportTest {
 		assertEquals(1, ByteUtils.readUint16(frame, 0)); // Payload length
 		assertEquals(maxPayloadLength - 1, ByteUtils.readUint16(frame, 2));
 	}
+
+	@Test
+	public void testGetCapacity() throws Exception {
+		int overheadPerFrame = 4 + mac.getMacLength();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		ConnectionEncrypter e = new NullConnectionEncrypter(out);
+		PaddedConnectionWriter w = new PaddedConnectionWriter(e, mac);
+		// Full frame
+		long capacity = w.getCapacity(MAX_FRAME_LENGTH);
+		assertEquals(MAX_FRAME_LENGTH - overheadPerFrame, capacity);
+		// Partial frame
+		capacity = w.getCapacity(overheadPerFrame + 1);
+		assertEquals(1, capacity);
+		// Full frame and partial frame
+		capacity = w.getCapacity(MAX_FRAME_LENGTH + 1);
+		assertEquals(MAX_FRAME_LENGTH + 1 - 2 * overheadPerFrame, capacity);
+		// Buffer some output
+		w.getOutputStream().write(0);
+		// Full frame minus buffered frame
+		capacity = w.getCapacity(MAX_FRAME_LENGTH);
+		assertEquals(MAX_FRAME_LENGTH - 1 - 2 * overheadPerFrame, capacity);
+		// Flush the buffer
+		w.writeFullFrame();
+		assertEquals(MAX_FRAME_LENGTH, out.size());
+		// Back to square one
+		capacity = w.getCapacity(MAX_FRAME_LENGTH);
+		assertEquals(MAX_FRAME_LENGTH - overheadPerFrame, capacity);
+	}
 }
-- 
GitLab