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