diff --git a/api/net/sf/briar/api/protocol/ProtocolConstants.java b/api/net/sf/briar/api/protocol/ProtocolConstants.java index 0a213a356186ab68bf7142c0a349f4f7e953db4c..a84f20e572fcdbb9de8de0ca118f16bb84ed899e 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 c7ed2038acf5e73f3c12356f6e72bcb89aa1e79d..e931cf7444271dd14c0406d3c7a4f82ecdb138dd 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 0aa0c01afa9cd143928e12bfc78db5314ffeefc6..7be2a44391eb8b5408d7b01200e36b92ee86cc7a 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 595307547b9a4c8be74e3a81d8b508829fba5dd2..bf272997bc6bdcb7cb82a28a494b476eb21047c5 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 c505deda338dd6c7a447a5ed594021e4d638ecc3..419445dcbd17fff4a2e61c89da76ac8ffd258cc1 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 d37f6ec20083b5f69e093dc5f6e90123ec8b0c9a..61d6364471a568004f888741d566502a617b3a68 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 60eea4fa5fbe9ef9deb58ec45d7528414ffedccc..ebab1f3eae286b99e7d2cd60f59d84d291efb8e1 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 985c962b0f051ba80913a587a792265be3ca4cd3..56528e9e249629caea68cd2bc6c4650b4536c9b0 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 c9f492d69bd7e99c346b4bdb0afc49c8d0f16a1d..7ba7b9d7a595873ca49d4be9582c5d8b3e3ff524 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 705e76a48d78b832683ca7bc3eae3c191a4ed2dc..8cdbb78f4d003c24393ab9d87628fab03450e6a6 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); + } }