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.
This commit is contained in:
akwizgran
2011-09-21 15:22:25 +01:00
parent 7e58b25618
commit 10c3b21726
10 changed files with 115 additions and 35 deletions

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}