mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 14:49:53 +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;
|
package net.sf.briar.api.protocol;
|
||||||
|
|
||||||
|
import net.sf.briar.api.transport.TransportConstants;
|
||||||
|
|
||||||
public interface ProtocolConstants {
|
public interface ProtocolConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum length of a serialised packet in bytes. Since the protocol
|
* The maximum length of a serialised packet in bytes. To allow for future
|
||||||
* does not aim for low latency, the two main constraints here are the
|
* changes in the protocol, this is smaller than the minimum connection
|
||||||
* amount of memory used for parsing packets and the granularity of the
|
* length minus the encryption and authentication overhead.
|
||||||
* database transactions for generating and receiving packets.
|
|
||||||
*/
|
*/
|
||||||
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.
|
* be written.
|
||||||
*/
|
*/
|
||||||
OutputStream getOutputStream();
|
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.
|
* 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
|
* The length in bytes of the encrypted IV that uniquely identifies a
|
||||||
* connection.
|
* connection.
|
||||||
*/
|
*/
|
||||||
static final int IV_LENGTH = 16;
|
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. */
|
/** Encrypts and writes the MAC for the current frame. */
|
||||||
void writeMac(byte[] mac) throws IOException;
|
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;
|
betweenFrames = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getCapacity(long capacity) {
|
||||||
|
if(capacity < 0L) throw new IllegalArgumentException();
|
||||||
|
return ivWritten ? capacity : Math.max(0L, capacity - IV_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int b) throws IOException {
|
public void write(int b) throws IOException {
|
||||||
if(!ivWritten) writeIv();
|
if(!ivWritten) writeIv();
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ import net.sf.briar.util.ByteUtils;
|
|||||||
class ConnectionWriterImpl extends FilterOutputStream
|
class ConnectionWriterImpl extends FilterOutputStream
|
||||||
implements ConnectionWriter {
|
implements ConnectionWriter {
|
||||||
|
|
||||||
private final ConnectionEncrypter encrypter;
|
protected final ConnectionEncrypter encrypter;
|
||||||
private final Mac mac;
|
protected final Mac mac;
|
||||||
private final int maxPayloadLength;
|
protected final int maxPayloadLength;
|
||||||
private final ByteArrayOutputStream buf;
|
protected final ByteArrayOutputStream buf;
|
||||||
private final byte[] header;
|
protected final byte[] header;
|
||||||
|
|
||||||
private long frame = 0L;
|
protected long frame = 0L;
|
||||||
|
|
||||||
ConnectionWriterImpl(ConnectionEncrypter encrypter, Mac mac) {
|
ConnectionWriterImpl(ConnectionEncrypter encrypter, Mac mac) {
|
||||||
super(encrypter.getOutputStream());
|
super(encrypter.getOutputStream());
|
||||||
@@ -41,6 +41,18 @@ implements ConnectionWriter {
|
|||||||
return this;
|
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
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
if(buf.size() > 0) writeFrame();
|
if(buf.size() > 0) writeFrame();
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
package net.sf.briar.transport;
|
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 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.IOException;
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
|
|
||||||
import net.sf.briar.api.transport.ConnectionWriter;
|
|
||||||
import net.sf.briar.util.ByteUtils;
|
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
|
* padding inserted if necessary. Calls to the writer's write() methods will
|
||||||
* block until there is space to buffer the data.
|
* block until there is space to buffer the data.
|
||||||
*/
|
*/
|
||||||
class PaddedConnectionWriter extends FilterOutputStream
|
class PaddedConnectionWriter extends ConnectionWriterImpl {
|
||||||
implements ConnectionWriter {
|
|
||||||
|
|
||||||
private final ConnectionEncrypter encrypter;
|
private final byte[] padding;
|
||||||
private final Mac mac;
|
|
||||||
private final int maxPayloadLength;
|
|
||||||
private final ByteArrayOutputStream buf;
|
|
||||||
private final byte[] header, padding;
|
|
||||||
|
|
||||||
private long frame = 0L;
|
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
private IOException exception = null;
|
private IOException exception = null;
|
||||||
|
|
||||||
PaddedConnectionWriter(ConnectionEncrypter encrypter, Mac mac) {
|
PaddedConnectionWriter(ConnectionEncrypter encrypter, Mac mac) {
|
||||||
super(encrypter.getOutputStream());
|
super(encrypter, mac);
|
||||||
this.encrypter = encrypter;
|
|
||||||
this.mac = mac;
|
|
||||||
maxPayloadLength = MAX_FRAME_LENGTH - 4 - mac.getMacLength();
|
|
||||||
buf = new ByteArrayOutputStream(maxPayloadLength);
|
|
||||||
header = new byte[4];
|
|
||||||
padding = new byte[maxPayloadLength];
|
padding = new byte[maxPayloadLength];
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputStream getOutputStream() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void close() throws IOException {
|
public synchronized void close() throws IOException {
|
||||||
if(exception != null) throw exception;
|
if(exception != null) throw exception;
|
||||||
|
|||||||
@@ -100,4 +100,32 @@ public class ConnectionWriterImplTest extends TransportTest {
|
|||||||
byte[] actual = out.toByteArray();
|
byte[] actual = out.toByteArray();
|
||||||
assertTrue(Arrays.equals(expected, actual));
|
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 {
|
public void writeMac(byte[] mac) throws IOException {
|
||||||
out.write(mac);
|
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(1, ByteUtils.readUint16(frame, 0)); // Payload length
|
||||||
assertEquals(maxPayloadLength - 1, ByteUtils.readUint16(frame, 2));
|
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