mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user