Plan B: Remove error correction and reliability layers and the

consequent distinction between segments and frames.
This commit is contained in:
akwizgran
2012-02-06 16:03:09 +00:00
parent 899ec5e19e
commit 6da30ca486
84 changed files with 296 additions and 2674 deletions

View File

@@ -33,12 +33,12 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String DIGEST_ALGO = "SHA-256";
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final String SEGMENT_CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
private static final String MAC_ALGO = "HMacSHA256";
// Labels for key derivation, null-terminated
private static final byte[] TAG = { 'T', 'A', 'G', 0 };
private static final byte[] SEGMENT = { 'S', 'E', 'G', 0 };
private static final byte[] FRAME = { 'F', 'R', 'A', 'M', 'E', 0 };
private static final byte[] MAC = { 'M', 'A', 'C', 0 };
private static final byte[] NEXT = { 'N', 'E', 'X', 'T', 0 };
// Context strings for key derivation
@@ -71,9 +71,9 @@ class CryptoComponentImpl implements CryptoComponent {
else return deriveKey(secret, TAG, RESPONDER);
}
public ErasableKey deriveSegmentKey(byte[] secret, boolean initiator) {
if(initiator) return deriveKey(secret, SEGMENT, INITIATOR);
else return deriveKey(secret, SEGMENT, RESPONDER);
public ErasableKey deriveFrameKey(byte[] secret, boolean initiator) {
if(initiator) return deriveKey(secret, FRAME, INITIATOR);
else return deriveKey(secret, FRAME, RESPONDER);
}
public ErasableKey deriveMacKey(byte[] secret, boolean initiator) {
@@ -168,9 +168,9 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
public Cipher getSegmentCipher() {
public Cipher getFrameCipher() {
try {
return Cipher.getInstance(SEGMENT_CIPHER_ALGO, PROVIDER);
return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}

View File

@@ -19,7 +19,7 @@ import net.sf.briar.api.transport.ConnectionWriterFactory;
import com.google.inject.Inject;
class StreamConnectionFactoryImpl implements DuplexConnectionFactory {
class DuplexConnectionFactoryImpl implements DuplexConnectionFactory {
private final Executor dbExecutor, verificationExecutor;
private final DatabaseComponent db;
@@ -30,7 +30,7 @@ class StreamConnectionFactoryImpl implements DuplexConnectionFactory {
private final ProtocolWriterFactory protoWriterFactory;
@Inject
StreamConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
DuplexConnectionFactoryImpl(@DatabaseExecutor Executor dbExecutor,
@VerificationExecutor Executor verificationExecutor,
DatabaseComponent db, ConnectionRegistry connRegistry,
ConnectionReaderFactory connReaderFactory,
@@ -48,11 +48,11 @@ class StreamConnectionFactoryImpl implements DuplexConnectionFactory {
}
public void createIncomingConnection(ConnectionContext ctx, TransportId t,
DuplexTransportConnection d, byte[] tag) {
DuplexTransportConnection d) {
final DuplexConnection conn = new IncomingDuplexConnection(dbExecutor,
verificationExecutor, db, connRegistry, connReaderFactory,
connWriterFactory, protoReaderFactory, protoWriterFactory,
ctx, t, d, tag);
ctx, t, d);
Runnable write = new Runnable() {
public void run() {
conn.write();

View File

@@ -10,6 +10,6 @@ public class DuplexProtocolModule extends AbstractModule {
@Override
protected void configure() {
bind(DuplexConnectionFactory.class).to(
StreamConnectionFactoryImpl.class).in(Singleton.class);
DuplexConnectionFactoryImpl.class).in(Singleton.class);
}
}

View File

@@ -20,7 +20,6 @@ import net.sf.briar.api.transport.ConnectionWriterFactory;
class IncomingDuplexConnection extends DuplexConnection {
private final ConnectionContext ctx;
private final byte[] tag;
IncomingDuplexConnection(@DatabaseExecutor Executor dbExecutor,
@VerificationExecutor Executor verificationExecutor,
@@ -30,18 +29,17 @@ class IncomingDuplexConnection extends DuplexConnection {
ProtocolReaderFactory protoReaderFactory,
ProtocolWriterFactory protoWriterFactory,
ConnectionContext ctx, TransportId transportId,
DuplexTransportConnection transport, byte[] tag) {
DuplexTransportConnection transport) {
super(dbExecutor, verificationExecutor, db, connRegistry,
connReaderFactory, connWriterFactory, protoReaderFactory,
protoWriterFactory, ctx.getContactId(), transportId, transport);
this.ctx = ctx;
this.tag = tag;
}
@Override
protected ConnectionReader createConnectionReader() throws IOException {
return connReaderFactory.createConnectionReader(
transport.getInputStream(), ctx.getSecret(), tag);
transport.getInputStream(), ctx.getSecret(), true);
}
@Override

View File

@@ -49,7 +49,7 @@ class OutgoingDuplexConnection extends DuplexConnection {
ctx = db.getConnectionContext(contactId, transportIndex);
}
return connReaderFactory.createConnectionReader(
transport.getInputStream(), ctx.getSecret());
transport.getInputStream(), ctx.getSecret(), false);
}
@Override

View File

@@ -40,7 +40,6 @@ class IncomingSimplexConnection {
private final ConnectionContext ctx;
private final TransportId transportId;
private final SimplexTransportReader transport;
private final byte[] tag;
private final ContactId contactId;
IncomingSimplexConnection(@DatabaseExecutor Executor dbExecutor,
@@ -48,8 +47,7 @@ class IncomingSimplexConnection {
DatabaseComponent db, ConnectionRegistry connRegistry,
ConnectionReaderFactory connFactory,
ProtocolReaderFactory protoFactory, ConnectionContext ctx,
TransportId transportId, SimplexTransportReader transport,
byte[] tag) {
TransportId transportId, SimplexTransportReader transport) {
this.dbExecutor = dbExecutor;
this.verificationExecutor = verificationExecutor;
this.db = db;
@@ -59,7 +57,6 @@ class IncomingSimplexConnection {
this.ctx = ctx;
this.transportId = transportId;
this.transport = transport;
this.tag = tag;
contactId = ctx.getContactId();
}
@@ -67,7 +64,7 @@ class IncomingSimplexConnection {
connRegistry.registerConnection(contactId, transportId);
try {
ConnectionReader conn = connFactory.createConnectionReader(
transport.getInputStream(), ctx.getSecret(), tag);
transport.getInputStream(), ctx.getSecret(), true);
InputStream in = conn.getInputStream();
ProtocolReader reader = protoFactory.createProtocolReader(in);
// Read packets until EOF

View File

@@ -49,10 +49,10 @@ class SimplexConnectionFactoryImpl implements SimplexConnectionFactory {
}
public void createIncomingConnection(ConnectionContext ctx, TransportId t,
SimplexTransportReader r, byte[] tag) {
SimplexTransportReader r) {
final IncomingSimplexConnection conn = new IncomingSimplexConnection(
dbExecutor, verificationExecutor, db, connRegistry,
connReaderFactory, protoReaderFactory, ctx, t, r, tag);
connReaderFactory, protoReaderFactory, ctx, t, r);
Runnable read = new Runnable() {
public void run() {
conn.read();

View File

@@ -93,7 +93,7 @@ class ConnectionDispatcherImpl implements ConnectionDispatcher {
tag);
if(ctx == null) transport.dispose(false, false);
else batchConnFactory.createIncomingConnection(ctx, transportId,
transport, tag);
transport);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
transport.dispose(true, false);
@@ -122,7 +122,7 @@ class ConnectionDispatcherImpl implements ConnectionDispatcher {
tag);
if(ctx == null) transport.dispose(false, false);
else streamConnFactory.createIncomingConnection(ctx,
transportId, transport, tag);
transportId, transport);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.toString());
transport.dispose(true, false);

View File

@@ -7,10 +7,8 @@ import javax.crypto.Mac;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.plugins.SegmentSource;
import net.sf.briar.api.transport.ConnectionReader;
import net.sf.briar.api.transport.ConnectionReaderFactory;
import net.sf.briar.api.transport.Segment;
import net.sf.briar.util.ByteUtils;
import com.google.inject.Inject;
@@ -25,76 +23,22 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
}
public ConnectionReader createConnectionReader(InputStream in,
byte[] secret, byte[] bufferedTag) {
return createConnectionReader(in, secret, bufferedTag, true);
}
public ConnectionReader createConnectionReader(InputStream in,
byte[] secret) {
return createConnectionReader(in, secret, null, false);
}
private ConnectionReader createConnectionReader(InputStream in,
byte[] secret, byte[] bufferedTag, boolean initiator) {
byte[] secret, boolean initiator) {
// Derive the keys and erase the secret
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
ErasableKey segKey = crypto.deriveSegmentKey(secret, initiator);
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret);
// Create the decrypter
// Encryption
Cipher tagCipher = crypto.getTagCipher();
Cipher segCipher = crypto.getSegmentCipher();
IncomingEncryptionLayer encryption = new IncomingEncryptionLayerImpl(in,
tagCipher, segCipher, tagKey, segKey, false, false,
bufferedTag);
// No error correction
IncomingErrorCorrectionLayer correction =
new NullIncomingErrorCorrectionLayer(encryption);
// Create the authenticator
Cipher frameCipher = crypto.getFrameCipher();
FrameReader encryption = new IncomingEncryptionLayerImpl(in, tagCipher,
frameCipher, tagKey, frameKey, !initiator);
// Authentication
Mac mac = crypto.getMac();
IncomingAuthenticationLayer authentication =
new IncomingAuthenticationLayerImpl(correction, mac, macKey, false);
// No reordering or retransmission
IncomingReliabilityLayer reliability =
new NullIncomingReliabilityLayer(authentication);
// Create the reader - don't tolerate errors
return new ConnectionReaderImpl(reliability, false, false);
}
public ConnectionReader createConnectionReader(SegmentSource in,
byte[] secret, Segment bufferedSegment) {
return createConnectionReader(in, secret, bufferedSegment, true);
}
public ConnectionReader createConnectionReader(SegmentSource in,
byte[] secret) {
return createConnectionReader(in, secret, null, false);
}
private ConnectionReader createConnectionReader(SegmentSource in,
byte[] secret, Segment bufferedSegment, boolean initiator) {
// Derive the keys and erase the secret
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
ErasableKey segKey = crypto.deriveSegmentKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret);
// Create the decrypter
Cipher tagCipher = crypto.getTagCipher();
Cipher segCipher = crypto.getSegmentCipher();
IncomingEncryptionLayer encryption =
new SegmentedIncomingEncryptionLayer(in, tagCipher, segCipher,
tagKey, segKey, false, false, bufferedSegment);
// No error correction
IncomingErrorCorrectionLayer correction =
new NullIncomingErrorCorrectionLayer(encryption);
// Create the authenticator
Mac mac = crypto.getMac();
IncomingAuthenticationLayer authentication =
new IncomingAuthenticationLayerImpl(correction, mac, macKey, false);
// No reordering or retransmission
IncomingReliabilityLayer reliability =
new NullIncomingReliabilityLayer(authentication);
// Create the reader - don't tolerate errors
return new ConnectionReaderImpl(reliability, false, false);
FrameReader authentication = new IncomingAuthenticationLayerImpl(
encryption, mac, macKey);
// Create the reader
return new ConnectionReaderImpl(authentication);
}
}

View File

@@ -1,30 +1,23 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import java.io.IOException;
import java.io.InputStream;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.transport.ConnectionReader;
class ConnectionReaderImpl extends InputStream implements ConnectionReader {
private final IncomingReliabilityLayer in;
private final boolean tolerateErrors;
private final int headerLength;
private final FrameReader in;
private final Frame frame;
private Frame frame;
private int offset = 0, length = 0;
ConnectionReaderImpl(IncomingReliabilityLayer in, boolean tolerateErrors,
boolean ackHeader) {
ConnectionReaderImpl(FrameReader in) {
this.in = in;
this.tolerateErrors = tolerateErrors;
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
frame = new Frame(in.getMaxFrameLength());
frame = new Frame();
offset = FRAME_HEADER_LENGTH;
}
public InputStream getInputStream() {
@@ -60,19 +53,14 @@ class ConnectionReaderImpl extends InputStream implements ConnectionReader {
private boolean readFrame() throws IOException {
assert length == 0;
while(true) {
try {
frame = in.readFrame(frame);
if(frame == null) {
length = -1;
return false;
}
offset = headerLength;
length = HeaderEncoder.getPayloadLength(frame.getBuffer());
return true;
} catch(InvalidDataException e) {
if(tolerateErrors) continue;
throw new FormatException();
frame.reset();
if(!in.readFrame(frame)) {
length = -1;
return false;
}
offset = FRAME_HEADER_LENGTH;
length = HeaderEncoder.getPayloadLength(frame.getBuffer());
return true;
}
}
}

View File

@@ -103,7 +103,7 @@ DatabaseListener {
private Bytes calculateTag(Context ctx, byte[] secret) {
ErasableKey tagKey = crypto.deriveTagKey(secret, true);
byte[] tag = new byte[TAG_LENGTH];
TagEncoder.encodeTag(tag, 0L, tagCipher, tagKey);
TagEncoder.encodeTag(tag, tagCipher, tagKey);
tagKey.erase();
return new Bytes(tag);
}

View File

@@ -7,7 +7,6 @@ import javax.crypto.Mac;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.plugins.SegmentSink;
import net.sf.briar.api.transport.ConnectionWriter;
import net.sf.briar.api.transport.ConnectionWriterFactory;
import net.sf.briar.util.ByteUtils;
@@ -27,52 +26,19 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
long capacity, byte[] secret, boolean initiator) {
// Derive the keys and erase the secret
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
ErasableKey segKey = crypto.deriveSegmentKey(secret, initiator);
ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret);
// Create the encrypter
// Encryption
Cipher tagCipher = crypto.getTagCipher();
Cipher segCipher = crypto.getSegmentCipher();
OutgoingEncryptionLayer encryption = new OutgoingEncryptionLayerImpl(
out, capacity, tagCipher, segCipher, tagKey, segKey, false);
// No error correction
OutgoingErrorCorrectionLayer correction =
new NullOutgoingErrorCorrectionLayer(encryption);
Cipher frameCipher = crypto.getFrameCipher();
FrameWriter encryption = new OutgoingEncryptionLayerImpl(
out, capacity, tagCipher, frameCipher, tagKey, frameKey);
// Authentication
Mac mac = crypto.getMac();
OutgoingAuthenticationLayer authentication =
new OutgoingAuthenticationLayerImpl(correction, mac, macKey);
// No retransmission
OutgoingReliabilityLayer reliability =
new NullOutgoingReliabilityLayer(authentication);
FrameWriter authentication =
new OutgoingAuthenticationLayerImpl(encryption, mac, macKey);
// Create the writer
return new ConnectionWriterImpl(reliability, false);
}
public ConnectionWriter createConnectionWriter(SegmentSink out,
long capacity, byte[] secret, boolean initiator) {
// Derive the keys and erase the secret
ErasableKey tagKey = crypto.deriveTagKey(secret, initiator);
ErasableKey segKey = crypto.deriveSegmentKey(secret, initiator);
ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
ByteUtils.erase(secret);
// Create the encrypter
Cipher tagCipher = crypto.getTagCipher();
Cipher segCipher = crypto.getSegmentCipher();
OutgoingEncryptionLayer encryption =
new SegmentedOutgoingEncryptionLayer(out, capacity, tagCipher,
segCipher, tagKey, segKey, false, false);
// No error correction
OutgoingErrorCorrectionLayer correction =
new NullOutgoingErrorCorrectionLayer(encryption);
// Authentication
Mac mac = crypto.getMac();
OutgoingAuthenticationLayer authentication =
new OutgoingAuthenticationLayerImpl(correction, mac, macKey);
// No retransmission
OutgoingReliabilityLayer reliability =
new NullOutgoingReliabilityLayer(authentication);
// Create the writer
return new ConnectionWriterImpl(reliability, false);
return new ConnectionWriterImpl(authentication);
}
}

View File

@@ -1,8 +1,8 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
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.IOException;
@@ -18,20 +18,16 @@ import net.sf.briar.api.transport.ConnectionWriter;
*/
class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
private final OutgoingReliabilityLayer out;
private final int headerLength, maxFrameLength;
private final FrameWriter out;
private final Frame frame;
private int offset;
private long frameNumber;
ConnectionWriterImpl(OutgoingReliabilityLayer out, boolean ackHeader) {
ConnectionWriterImpl(FrameWriter out) {
this.out = out;
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
maxFrameLength = out.getMaxFrameLength();
frame = new Frame(maxFrameLength);
offset = headerLength;
frame = new Frame();
offset = FRAME_HEADER_LENGTH;
frameNumber = 0L;
}
@@ -42,23 +38,23 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
public long getRemainingCapacity() {
long capacity = out.getRemainingCapacity();
// If there's any data buffered, subtract it and its overhead
if(offset > headerLength) capacity -= offset + MAC_LENGTH;
if(offset > FRAME_HEADER_LENGTH) capacity -= offset + MAC_LENGTH;
// Subtract the overhead from the remaining capacity
long frames = (long) Math.ceil((double) capacity / maxFrameLength);
int overheadPerFrame = headerLength + MAC_LENGTH;
long frames = (long) Math.ceil((double) capacity / MAX_FRAME_LENGTH);
int overheadPerFrame = FRAME_HEADER_LENGTH + MAC_LENGTH;
return Math.max(0L, capacity - frames * overheadPerFrame);
}
@Override
public void flush() throws IOException {
if(offset > headerLength) writeFrame();
if(offset > FRAME_HEADER_LENGTH) writeFrame();
out.flush();
}
@Override
public void write(int b) throws IOException {
frame.getBuffer()[offset++] = (byte) b;
if(offset + MAC_LENGTH == maxFrameLength) writeFrame();
if(offset + MAC_LENGTH == MAX_FRAME_LENGTH) writeFrame();
}
@Override
@@ -69,14 +65,14 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
@Override
public void write(byte[] b, int off, int len) throws IOException {
byte[] buf = frame.getBuffer();
int available = maxFrameLength - offset - MAC_LENGTH;
int available = MAX_FRAME_LENGTH - offset - MAC_LENGTH;
while(available <= len) {
System.arraycopy(b, off, buf, offset, available);
offset += available;
writeFrame();
off += available;
len -= available;
available = maxFrameLength - offset - MAC_LENGTH;
available = MAX_FRAME_LENGTH - offset - MAC_LENGTH;
}
System.arraycopy(b, off, buf, offset, len);
offset += len;
@@ -84,12 +80,13 @@ class ConnectionWriterImpl extends OutputStream implements ConnectionWriter {
private void writeFrame() throws IOException {
if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
int payload = offset - headerLength;
int payload = offset - FRAME_HEADER_LENGTH;
assert payload > 0;
HeaderEncoder.encodeHeader(frame.getBuffer(), frameNumber, payload, 0);
frame.setLength(offset + MAC_LENGTH);
out.writeFrame(frame);
offset = headerLength;
frame.reset();
offset = FRAME_HEADER_LENGTH;
frameNumber++;
}
}

View File

@@ -1,13 +0,0 @@
package net.sf.briar.transport;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.transport.Segment;
interface ErasureDecoder {
/**
* Decodes the given set of segments into the given frame, or returns false
* if the segments cannot be decoded. The segment set may contain nulls.
*/
public boolean decodeFrame(Frame f, Segment[] set) throws FormatException;
}

View File

@@ -1,9 +0,0 @@
package net.sf.briar.transport;
import net.sf.briar.api.transport.Segment;
interface ErasureEncoder {
/** Encodes the given frame as a set of segments. */
Segment[] encodeFrame(Frame f);
}

View File

@@ -3,7 +3,6 @@ package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
class Frame {
@@ -12,25 +11,13 @@ class Frame {
private int length = -1;
Frame() {
this(MAX_FRAME_LENGTH);
}
Frame(int length) {
if(length < FRAME_HEADER_LENGTH + MAC_LENGTH)
throw new IllegalArgumentException();
if(length > MAX_SEGMENT_LENGTH) throw new IllegalArgumentException();
buf = new byte[length];
buf = new byte[MAX_FRAME_LENGTH];
}
public byte[] getBuffer() {
return buf;
}
public long getFrameNumber() {
if(length == -1) throw new IllegalStateException();
return HeaderEncoder.getFrameNumber(buf);
}
public int getLength() {
if(length == -1) throw new IllegalStateException();
return length;
@@ -41,4 +28,8 @@ class Frame {
throw new IllegalArgumentException();
this.length = length;
}
public void reset() {
length = -1;
}
}

View File

@@ -0,0 +1,12 @@
package net.sf.briar.transport;
import java.io.IOException;
interface FrameReader {
/**
* Reads a frame into the given buffer. Returns false if no more frames can
* be read from the connection.
*/
boolean readFrame(Frame f) throws IOException;
}

View File

@@ -1,16 +0,0 @@
package net.sf.briar.transport;
interface FrameWindow {
/** Returns true if the given number is too high to fit in the window. */
boolean isTooHigh(long frameNumber);
/** Returns true if the given number is in the window. */
boolean contains(long frameNumber);
/**
* Removes the given number from the window and advances the window.
* Returns false if the given number is not in the window.
*/
boolean remove(long frameNumber);
}

View File

@@ -1,62 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.FRAME_WINDOW_SIZE;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.util.Collection;
import java.util.HashSet;
/** A frame window that allows a limited amount of reordering. */
class FrameWindowImpl implements FrameWindow {
private final Collection<Long> window;
private long base;
FrameWindowImpl() {
window = new HashSet<Long>();
fill(0, FRAME_WINDOW_SIZE);
base = 0;
}
public boolean isTooHigh(long frameNumber) {
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return frameNumber >= base + FRAME_WINDOW_SIZE;
}
public boolean contains(long frameNumber) {
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return window.contains(frameNumber);
}
public boolean remove(long frameNumber) {
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
if(!window.remove(frameNumber)) return false;
if(frameNumber == base) {
// Find the new base
if(window.isEmpty()) {
base += FRAME_WINDOW_SIZE;
fill(base, base + FRAME_WINDOW_SIZE);
} else {
for(long l = base; l < base + FRAME_WINDOW_SIZE; l++) {
if(window.contains(l)) {
fill(base + FRAME_WINDOW_SIZE, l + FRAME_WINDOW_SIZE);
base = l;
break;
}
}
}
}
return true;
}
private void fill(long from, long to) {
for(long l = from; l < to; l++) {
if(l <= MAX_32_BIT_UNSIGNED) window.add(l);
else return;
}
}
}

View File

@@ -2,7 +2,7 @@ package net.sf.briar.transport;
import java.io.IOException;
interface OutgoingReliabilityLayer {
interface FrameWriter {
/** Writes the given frame. */
void writeFrame(Frame f) throws IOException;
@@ -12,7 +12,4 @@ interface OutgoingReliabilityLayer {
/** Returns the maximum number of bytes that can be written. */
long getRemainingCapacity();
/** Returns the maximum length in bytes of the frames this layer accepts. */
int getMaxFrameLength();
}

View File

@@ -1,21 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
interface IncomingAuthenticationLayer {
/**
* Reads a frame into the given buffer. The frame number must be contained
* in the given window. Returns false if no more frames can be read from
* the connection.
* @throws IOException if an unrecoverable error occurs and the connection
* must be closed.
* @throws InvalidDataException if a recoverable error occurs. The caller
* may choose whether to retry the read or close the connection.
*/
boolean readFrame(Frame f, FrameWindow window) throws IOException,
InvalidDataException;
/** Returns the maximum length in bytes of the frames this layer returns. */
int getMaxFrameLength();
}

View File

@@ -1,24 +1,24 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.IOException;
import java.security.InvalidKeyException;
import javax.crypto.Mac;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.crypto.ErasableKey;
class IncomingAuthenticationLayerImpl implements IncomingAuthenticationLayer {
class IncomingAuthenticationLayerImpl implements FrameReader {
private final IncomingErrorCorrectionLayer in;
private final FrameReader in;
private final Mac mac;
private final int headerLength, maxFrameLength;
IncomingAuthenticationLayerImpl(IncomingErrorCorrectionLayer in, Mac mac,
ErasableKey macKey, boolean ackHeader) {
IncomingAuthenticationLayerImpl(FrameReader in, Mac mac,
ErasableKey macKey) {
this.in = in;
this.mac = mac;
try {
@@ -29,43 +29,34 @@ class IncomingAuthenticationLayerImpl implements IncomingAuthenticationLayer {
macKey.erase();
if(mac.getMacLength() != MAC_LENGTH)
throw new IllegalArgumentException();
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
maxFrameLength = in.getMaxFrameLength();
}
public boolean readFrame(Frame f, FrameWindow window) throws IOException,
InvalidDataException {
public boolean readFrame(Frame f) throws IOException {
// Read a frame
if(!in.readFrame(f, window)) return false;
if(!in.readFrame(f)) return false;
// Check that the length is legal
int length = f.getLength();
if(length < headerLength + MAC_LENGTH)
throw new InvalidDataException();
if(length > maxFrameLength) throw new InvalidDataException();
if(length < FRAME_HEADER_LENGTH + MAC_LENGTH)
throw new FormatException();
if(length > MAX_FRAME_LENGTH) throw new FormatException();
// Check that the payload and padding lengths are correct
byte[] buf = f.getBuffer();
int payload = HeaderEncoder.getPayloadLength(buf);
int padding = HeaderEncoder.getPaddingLength(buf);
if(length != headerLength + payload + padding + MAC_LENGTH)
throw new InvalidDataException();
if(length != FRAME_HEADER_LENGTH + payload + padding + MAC_LENGTH)
throw new FormatException();
// Check that the padding is all zeroes
int paddingStart = headerLength + payload;
int paddingStart = FRAME_HEADER_LENGTH + payload;
for(int i = paddingStart; i < paddingStart + padding; i++) {
if(buf[i] != 0) throw new InvalidDataException();
if(buf[i] != 0) throw new FormatException();
}
// Verify the MAC
int macStart = headerLength + payload + padding;
int macStart = FRAME_HEADER_LENGTH + payload + padding;
mac.update(buf, 0, macStart);
byte[] expectedMac = mac.doFinal();
for(int i = 0; i < expectedMac.length; i++) {
if(expectedMac[i] != buf[macStart + i])
throw new InvalidDataException();
if(expectedMac[i] != buf[macStart + i]) throw new FormatException();
}
return true;
}
public int getMaxFrameLength() {
return maxFrameLength;
}
}

View File

@@ -1,23 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
import net.sf.briar.api.transport.Segment;
interface IncomingEncryptionLayer {
/**
* Reads a segment, excluding its tag, into the given buffer. Returns false
* if no more segments can be read from the connection.
* @throws IOException if an unrecoverable error occurs and the connection
* must be closed.
* @throws InvalidDataException if a recoverable error occurs. The caller
* may choose whether to retry the read or close the connection.
*/
boolean readSegment(Segment s) throws IOException, InvalidDataException;
/**
* Returns the maximum length in bytes of the segments this layer returns.
*/
int getMaxSegmentLength();
}

View File

@@ -1,10 +1,8 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.EOFException;
@@ -17,86 +15,70 @@ import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.transport.Segment;
class IncomingEncryptionLayerImpl implements IncomingEncryptionLayer {
class IncomingEncryptionLayerImpl implements FrameReader {
private final InputStream in;
private final Cipher tagCipher, segCipher;
private final ErasableKey tagKey, segKey;
private final boolean tagEverySegment;
private final int headerLength, blockSize;
private final Cipher tagCipher, frameCipher;
private final ErasableKey tagKey, frameKey;
private final int blockSize;
private final byte[] iv, ciphertext;
private byte[] bufferedTag;
private boolean firstSegment = true;
private long segmentNumber = 0L;
private boolean readTag;
private long frameNumber;
IncomingEncryptionLayerImpl(InputStream in, Cipher tagCipher,
Cipher segCipher, ErasableKey tagKey, ErasableKey segKey,
boolean tagEverySegment, boolean ackHeader, byte[] bufferedTag) {
Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey,
boolean readTag) {
this.in = in;
this.tagCipher = tagCipher;
this.segCipher = segCipher;
this.frameCipher = frameCipher;
this.tagKey = tagKey;
this.segKey = segKey;
this.tagEverySegment = tagEverySegment;
this.bufferedTag = bufferedTag;
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
blockSize = segCipher.getBlockSize();
this.frameKey = frameKey;
this.readTag = readTag;
blockSize = frameCipher.getBlockSize();
if(blockSize < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException();
iv = IvEncoder.encodeIv(0L, blockSize);
ciphertext = new byte[MAX_SEGMENT_LENGTH];
ciphertext = new byte[MAX_FRAME_LENGTH];
frameNumber = 0L;
}
public boolean readSegment(Segment s) throws IOException {
boolean expectTag = tagEverySegment || firstSegment;
firstSegment = false;
public boolean readFrame(Frame f) throws IOException {
try {
if(expectTag) {
// Read the tag if we don't have one buffered
if(bufferedTag == null) {
int offset = 0;
while(offset < TAG_LENGTH) {
int read = in.read(ciphertext, offset,
TAG_LENGTH - offset);
if(read == -1) {
if(offset == 0) return false;
throw new EOFException();
}
offset += read;
// Read the tag if it hasn't already been read
if(readTag) {
int offset = 0;
while(offset < TAG_LENGTH) {
int read = in.read(ciphertext, offset,
TAG_LENGTH - offset);
if(read == -1) {
if(offset == 0) return false;
throw new EOFException();
}
long seg = TagEncoder.decodeTag(ciphertext, tagCipher,
tagKey);
if(seg == -1) throw new FormatException();
segmentNumber = seg;
} else {
long seg = TagEncoder.decodeTag(bufferedTag, tagCipher,
tagKey);
bufferedTag = null;
if(seg == -1) throw new FormatException();
segmentNumber = seg;
offset += read;
}
if(!TagEncoder.decodeTag(ciphertext, tagCipher, tagKey))
throw new FormatException();
}
// Read the first block of the frame
int offset = 0;
while(offset < blockSize) {
int read = in.read(ciphertext, offset, blockSize - offset);
if(read == -1) {
if(offset == 0 && !expectTag) return false;
if(offset == 0 && !readTag) return false;
throw new EOFException();
}
offset += read;
}
readTag = false;
// Decrypt the first block of the frame
byte[] plaintext = s.getBuffer();
byte[] plaintext = f.getBuffer();
try {
IvEncoder.updateIv(iv, segmentNumber);
IvEncoder.updateIv(iv, frameNumber);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
segCipher.init(Cipher.DECRYPT_MODE, segKey, ivSpec);
int decrypted = segCipher.update(ciphertext, 0, blockSize,
frameCipher.init(Cipher.DECRYPT_MODE, frameKey, ivSpec);
int decrypted = frameCipher.update(ciphertext, 0, blockSize,
plaintext);
if(decrypted != blockSize) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
@@ -105,7 +87,7 @@ class IncomingEncryptionLayerImpl implements IncomingEncryptionLayer {
// Parse the frame header
int payload = HeaderEncoder.getPayloadLength(plaintext);
int padding = HeaderEncoder.getPaddingLength(plaintext);
int length = headerLength + payload + padding + MAC_LENGTH;
int length = FRAME_HEADER_LENGTH + payload + padding + MAC_LENGTH;
if(length > MAX_FRAME_LENGTH) throw new FormatException();
// Read the remainder of the frame
while(offset < length) {
@@ -115,24 +97,20 @@ class IncomingEncryptionLayerImpl implements IncomingEncryptionLayer {
}
// Decrypt the remainder of the frame
try {
int decrypted = segCipher.doFinal(ciphertext, blockSize,
int decrypted = frameCipher.doFinal(ciphertext, blockSize,
length - blockSize, plaintext, blockSize);
if(decrypted != length - blockSize)
throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
s.setLength(length);
s.setSegmentNumber(segmentNumber++);
f.setLength(length);
frameNumber++;
return true;
} catch(IOException e) {
segKey.erase();
frameKey.erase();
tagKey.erase();
throw e;
}
}
public int getMaxSegmentLength() {
return MAX_SEGMENT_LENGTH - TAG_LENGTH;
}
}

View File

@@ -1,21 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
interface IncomingErrorCorrectionLayer {
/**
* Reads a frame into the given buffer. The frame number must be contained
* in the given window. Returns false if no more frames can be read from
* the connection.
* @throws IOException if an unrecoverable error occurs and the connection
* must be closed.
* @throws InvalidDataException if a recoverable error occurs. The caller
* may choose whether to retry the read or close the connection.
*/
boolean readFrame(Frame f, FrameWindow window) throws IOException,
InvalidDataException;
/** Returns the maximum length in bytes of the frames this layer returns. */
int getMaxFrameLength();
}

View File

@@ -1,101 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.transport.Segment;
class IncomingErrorCorrectionLayerImpl implements IncomingErrorCorrectionLayer {
private final IncomingEncryptionLayer in;
private final ErasureDecoder decoder;
private final int n, k, maxSegmentLength, maxFrameLength;
private final Map<Long, Integer> discardCounts;
private final Map<Long, Segment[]> segmentSets;
private final ArrayList<Segment> freeSegments;
IncomingErrorCorrectionLayerImpl(IncomingEncryptionLayer in,
ErasureDecoder decoder, int n, int k) {
this.in = in;
this.decoder = decoder;
this.n = n;
this.k = k;
maxSegmentLength = in.getMaxSegmentLength();
maxFrameLength = Math.min(MAX_FRAME_LENGTH, maxSegmentLength * k);
discardCounts = new HashMap<Long, Integer>();
segmentSets = new HashMap<Long, Segment[]>();
freeSegments = new ArrayList<Segment>();
}
public boolean readFrame(Frame f, FrameWindow window) throws IOException,
InvalidDataException {
// Free any segment sets that have been removed from the window
Iterator<Entry<Long, Segment[]>> it = segmentSets.entrySet().iterator();
while(it.hasNext()) {
Entry<Long, Segment[]> e = it.next();
if(!window.contains(e.getKey())) {
it.remove();
for(Segment s : e.getValue()) if(s != null) freeSegments.add(s);
}
}
// Free any discard counts that are no longer too high for the window
Iterator<Long> it1 = discardCounts.keySet().iterator();
while(it1.hasNext()) if(!window.isTooHigh(it1.next())) it1.remove();
// Grab a free segment, or allocate one if necessary
Segment s;
int free = freeSegments.size();
if(free == 0) s = new SegmentImpl(maxSegmentLength);
else s = freeSegments.remove(free - 1);
// Read segments until a frame can be decoded
while(true) {
// Read segments until a segment in the window is returned
long frameNumber;
while(true) {
if(!in.readSegment(s)) {
freeSegments.add(s);
return false;
}
frameNumber = s.getSegmentNumber() / n;
if(window.contains(frameNumber)) break;
if(window.isTooHigh(frameNumber)) countDiscard(frameNumber);
}
// Add the segment to its set, creating a set if necessary
Segment[] set = segmentSets.get(frameNumber);
if(set == null) {
set = new Segment[n];
segmentSets.put(frameNumber, set);
}
set[(int) (frameNumber % n)] = s;
// Try to decode the frame
if(decoder.decodeFrame(f, set)) return true;
}
}
public int getMaxFrameLength() {
return maxFrameLength;
}
private void countDiscard(long frameNumber) throws FormatException {
Integer count = discardCounts.get(frameNumber);
if(count == null) discardCounts.put(frameNumber, 1);
else if(count == n - k) throw new FormatException();
else discardCounts.put(frameNumber, count + 1);
}
// Only for testing
Map<Long, Segment[]> getSegmentSets() {
return segmentSets;
}
// Only for testing
Map<Long, Integer> getDiscardCounts() {
return discardCounts;
}
}

View File

@@ -1,19 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
interface IncomingReliabilityLayer {
/**
* Reads and returns a frame, possibly using the given buffer. Returns null
* if no more frames can be read from the connection.
* @throws IOException if an unrecoverable error occurs and the connection
* must be closed.
* @throws InvalidDataException if a recoverable error occurs. The caller
* may choose whether to retry the read or close the connection.
*/
Frame readFrame(Frame f) throws IOException, InvalidDataException;
/** Returns the maximum length in bytes of the frames this layer returns. */
int getMaxFrameLength();
}

View File

@@ -1,62 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.SortedMap;
import java.util.TreeMap;
/** A reliability layer that reorders out-of-order frames. */
class IncomingReliabilityLayerImpl implements IncomingReliabilityLayer {
private final IncomingAuthenticationLayer in;
private final int maxFrameLength;
private final FrameWindow window;
private final SortedMap<Long, Frame> frames;
private final ArrayList<Frame> freeFrames;
private long nextFrameNumber = 0L;
IncomingReliabilityLayerImpl(IncomingAuthenticationLayer in) {
this.in = in;
maxFrameLength = in.getMaxFrameLength();
window = new FrameWindowImpl();
frames = new TreeMap<Long, Frame>();
freeFrames = new ArrayList<Frame>();
}
public Frame readFrame(Frame f) throws IOException,
InvalidDataException {
freeFrames.add(f);
// Read frames until there's an in-order frame to return
while(frames.isEmpty() || frames.firstKey() > nextFrameNumber) {
// Grab a free frame, or allocate one if necessary
int free = freeFrames.size();
if(free == 0) f = new Frame(maxFrameLength);
else f = freeFrames.remove(free - 1);
// Read a frame
if(!in.readFrame(f, window)) return null;
// If the frame is in order, return it
long frameNumber = f.getFrameNumber();
if(frameNumber == nextFrameNumber) {
if(!window.remove(nextFrameNumber))
throw new IllegalStateException();
nextFrameNumber++;
return f;
}
// Insert the frame into the map
frames.put(frameNumber, f);
}
if(!window.remove(nextFrameNumber)) throw new IllegalStateException();
nextFrameNumber++;
return frames.remove(frames.firstKey());
}
public int getMaxFrameLength() {
return maxFrameLength;
}
// Only for testing
public int getFreeFramesCount() {
return freeFrames.size();
}
}

View File

@@ -1,7 +0,0 @@
package net.sf.briar.transport;
/** An exception that indicates a recoverable formatting error. */
class InvalidDataException extends Exception {
private static final long serialVersionUID = 4455775710413826953L;
}

View File

@@ -1,29 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
/** A frame window that does not allow any reordering. */
class NullFrameWindow implements FrameWindow {
private long base = 0L;
public boolean isTooHigh(long frameNumber) {
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return frameNumber != base;
}
public boolean contains(long frameNumber) {
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return frameNumber == base;
}
public boolean remove(long frameNumber) {
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
if(frameNumber != base) return false;
base++;
return true;
}
}

View File

@@ -1,37 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
import net.sf.briar.api.transport.Segment;
class NullIncomingErrorCorrectionLayer implements IncomingErrorCorrectionLayer {
private final IncomingEncryptionLayer in;
private final int maxFrameLength;
private final Segment segment;
NullIncomingErrorCorrectionLayer(IncomingEncryptionLayer in) {
this.in = in;
maxFrameLength = in.getMaxSegmentLength();
segment = new SegmentImpl(maxFrameLength);
}
public boolean readFrame(Frame f, FrameWindow window) throws IOException,
InvalidDataException {
while(true) {
if(!in.readSegment(segment)) return false;
byte[] buf = segment.getBuffer();
long frameNumber = HeaderEncoder.getFrameNumber(buf);
if(window.contains(frameNumber)) break;
}
int length = segment.getLength();
// FIXME: Unnecessary copy
System.arraycopy(segment.getBuffer(), 0, f.getBuffer(), 0, length);
f.setLength(length);
return true;
}
public int getMaxFrameLength() {
return maxFrameLength;
}
}

View File

@@ -1,27 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
class NullIncomingReliabilityLayer implements IncomingReliabilityLayer {
private final IncomingAuthenticationLayer in;
private final int maxFrameLength;
private final FrameWindow window;
NullIncomingReliabilityLayer(IncomingAuthenticationLayer in) {
this.in = in;
maxFrameLength = in.getMaxFrameLength();
window = new NullFrameWindow();
}
public Frame readFrame(Frame f) throws IOException, InvalidDataException {
if(!in.readFrame(f, window)) return null;
if(!window.remove(f.getFrameNumber()))
throw new IllegalStateException();
return f;
}
public int getMaxFrameLength() {
return maxFrameLength;
}
}

View File

@@ -1,45 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.IOException;
import net.sf.briar.api.transport.Segment;
class NullOutgoingErrorCorrectionLayer implements OutgoingErrorCorrectionLayer {
private final OutgoingEncryptionLayer out;
private final int maxSegmentLength;
private final Segment segment;
private long segmentNumber = 0L;
public NullOutgoingErrorCorrectionLayer(OutgoingEncryptionLayer out) {
this.out = out;
maxSegmentLength = out.getMaxSegmentLength();
segment = new SegmentImpl(maxSegmentLength);
}
public void writeFrame(Frame f) throws IOException {
if(segmentNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalStateException();
int length = f.getLength();
// FIXME: Unnecessary copy
System.arraycopy(f.getBuffer(), 0, segment.getBuffer(), 0, length);
segment.setLength(length);
segment.setSegmentNumber(segmentNumber++);
out.writeSegment(segment);
}
public void flush() throws IOException {
out.flush();
}
public long getRemainingCapacity() {
return out.getRemainingCapacity();
}
public int getMaxFrameLength() {
return maxSegmentLength;
}
}

View File

@@ -1,30 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
class NullOutgoingReliabilityLayer implements OutgoingReliabilityLayer {
private final OutgoingAuthenticationLayer out;
private final int maxFrameLength;
NullOutgoingReliabilityLayer(OutgoingAuthenticationLayer out) {
this.out = out;
maxFrameLength = out.getMaxFrameLength();
}
public void writeFrame(Frame f) throws IOException {
out.writeFrame(f);
}
public void flush() throws IOException {
out.flush();
}
public long getRemainingCapacity() {
return out.getRemainingCapacity();
}
public int getMaxFrameLength() {
return maxFrameLength;
}
}

View File

@@ -1,18 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
interface OutgoingAuthenticationLayer {
/** Writes the given frame. */
void writeFrame(Frame f) throws IOException;
/** Flushes the stack. */
void flush() throws IOException;
/** Returns the maximum number of bytes that can be written. */
long getRemainingCapacity();
/** Returns the maximum length in bytes of the frames this layer accepts. */
int getMaxFrameLength();
}

View File

@@ -10,13 +10,12 @@ import javax.crypto.ShortBufferException;
import net.sf.briar.api.crypto.ErasableKey;
class OutgoingAuthenticationLayerImpl implements OutgoingAuthenticationLayer {
class OutgoingAuthenticationLayerImpl implements FrameWriter {
private final OutgoingErrorCorrectionLayer out;
private final FrameWriter out;
private final Mac mac;
private final int maxFrameLength;
OutgoingAuthenticationLayerImpl(OutgoingErrorCorrectionLayer out, Mac mac,
OutgoingAuthenticationLayerImpl(FrameWriter out, Mac mac,
ErasableKey macKey) {
this.out = out;
this.mac = mac;
@@ -28,7 +27,6 @@ class OutgoingAuthenticationLayerImpl implements OutgoingAuthenticationLayer {
macKey.erase();
if(mac.getMacLength() != MAC_LENGTH)
throw new IllegalArgumentException();
maxFrameLength = out.getMaxFrameLength();
}
public void writeFrame(Frame f) throws IOException {
@@ -50,8 +48,4 @@ class OutgoingAuthenticationLayerImpl implements OutgoingAuthenticationLayer {
public long getRemainingCapacity() {
return out.getRemainingCapacity();
}
public int getMaxFrameLength() {
return maxFrameLength;
}
}

View File

@@ -1,22 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
import net.sf.briar.api.transport.Segment;
interface OutgoingEncryptionLayer {
/** Writes the given segment. */
void writeSegment(Segment s) throws IOException;
/** Flushes the stack. */
void flush() throws IOException;
/** Returns the maximum number of bytes that can be written. */
long getRemainingCapacity();
/**
* Returns the maximum length in bytes of the segments this layer accepts.
*/
int getMaxSegmentLength();
}

View File

@@ -1,6 +1,6 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.IOException;
@@ -11,46 +11,43 @@ import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.transport.Segment;
class OutgoingEncryptionLayerImpl implements OutgoingEncryptionLayer {
class OutgoingEncryptionLayerImpl implements FrameWriter {
private final OutputStream out;
private final Cipher tagCipher, segCipher;
private final ErasableKey tagKey, segKey;
private final boolean tagEverySegment;
private final Cipher tagCipher, frameCipher;
private final ErasableKey tagKey, frameKey;
private final byte[] iv, ciphertext;
private long capacity;
private long capacity, frameNumber;
OutgoingEncryptionLayerImpl(OutputStream out, long capacity,
Cipher tagCipher, Cipher segCipher, ErasableKey tagKey,
ErasableKey segKey, boolean tagEverySegment) {
Cipher tagCipher, Cipher frameCipher, ErasableKey tagKey,
ErasableKey frameKey) {
this.out = out;
this.capacity = capacity;
this.tagCipher = tagCipher;
this.segCipher = segCipher;
this.frameCipher = frameCipher;
this.tagKey = tagKey;
this.segKey = segKey;
this.tagEverySegment = tagEverySegment;
iv = IvEncoder.encodeIv(0L, segCipher.getBlockSize());
ciphertext = new byte[MAX_SEGMENT_LENGTH];
this.frameKey = frameKey;
iv = IvEncoder.encodeIv(0L, frameCipher.getBlockSize());
ciphertext = new byte[TAG_LENGTH + MAX_FRAME_LENGTH];
frameNumber = 0L;
}
public void writeSegment(Segment s) throws IOException {
byte[] plaintext = s.getBuffer();
int length = s.getLength();
long segmentNumber = s.getSegmentNumber();
public void writeFrame(Frame f) throws IOException {
byte[] plaintext = f.getBuffer();
int length = f.getLength();
int offset = 0;
if(tagEverySegment || segmentNumber == 0) {
TagEncoder.encodeTag(ciphertext, segmentNumber, tagCipher, tagKey);
if(frameNumber == 0) {
TagEncoder.encodeTag(ciphertext, tagCipher, tagKey);
offset = TAG_LENGTH;
}
IvEncoder.updateIv(iv, segmentNumber);
IvEncoder.updateIv(iv, frameNumber);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
try {
segCipher.init(Cipher.ENCRYPT_MODE, segKey, ivSpec);
int encrypted = segCipher.doFinal(plaintext, 0, length,
frameCipher.init(Cipher.ENCRYPT_MODE, frameKey, ivSpec);
int encrypted = frameCipher.doFinal(plaintext, 0, length,
ciphertext, offset);
if(encrypted != length) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
@@ -59,11 +56,12 @@ class OutgoingEncryptionLayerImpl implements OutgoingEncryptionLayer {
try {
out.write(ciphertext, 0, offset + length);
} catch(IOException e) {
segKey.erase();
frameKey.erase();
tagKey.erase();
throw e;
}
capacity -= offset + length;
frameNumber++;
}
public void flush() throws IOException {
@@ -73,8 +71,4 @@ class OutgoingEncryptionLayerImpl implements OutgoingEncryptionLayer {
public long getRemainingCapacity() {
return capacity;
}
public int getMaxSegmentLength() {
return MAX_SEGMENT_LENGTH - TAG_LENGTH;
}
}

View File

@@ -1,18 +0,0 @@
package net.sf.briar.transport;
import java.io.IOException;
interface OutgoingErrorCorrectionLayer {
/** Writes the given frame. */
void writeFrame(Frame f) throws IOException;
/** Flushes the stack. */
void flush() throws IOException;
/** Returns the maximum number of bytes that can be written. */
long getRemainingCapacity();
/** Returns the maximum length in bytes of the frames this layer accepts. */
int getMaxFrameLength();
}

View File

@@ -1,39 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.io.IOException;
import net.sf.briar.api.transport.Segment;
class OutgoingErrorCorrectionLayerImpl implements OutgoingErrorCorrectionLayer {
private final OutgoingEncryptionLayer out;
private final ErasureEncoder encoder;
private final int n, maxFrameLength;
OutgoingErrorCorrectionLayerImpl(OutgoingEncryptionLayer out,
ErasureEncoder encoder, int n, int k) {
this.out = out;
this.encoder = encoder;
this.n = n;
maxFrameLength = Math.min(MAX_FRAME_LENGTH,
out.getMaxSegmentLength() * k);
}
public void writeFrame(Frame f) throws IOException {
for(Segment s : encoder.encodeFrame(f)) out.writeSegment(s);
}
public void flush() throws IOException {
out.flush();
}
public long getRemainingCapacity() {
return out.getRemainingCapacity() / n;
}
public int getMaxFrameLength() {
return maxFrameLength;
}
}

View File

@@ -1,52 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import net.sf.briar.api.transport.Segment;
class SegmentImpl implements Segment {
private final byte[] buf;
private int length = -1;
private long segmentNumber = -1;
SegmentImpl() {
this(MAX_SEGMENT_LENGTH);
}
SegmentImpl(int length) {
if(length < FRAME_HEADER_LENGTH + MAC_LENGTH)
throw new IllegalArgumentException();
if(length > MAX_SEGMENT_LENGTH) throw new IllegalArgumentException();
buf = new byte[length];
}
public byte[] getBuffer() {
return buf;
}
public int getLength() {
if(length == -1) throw new IllegalStateException();
return length;
}
public long getSegmentNumber() {
if(segmentNumber == -1) throw new IllegalStateException();
return segmentNumber;
}
public void setLength(int length) {
if(length < FRAME_HEADER_LENGTH + MAC_LENGTH || length > buf.length)
throw new IllegalArgumentException();
this.length = length;
}
public void setSegmentNumber(long segmentNumber) {
if(segmentNumber < 0 || segmentNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
this.segmentNumber = segmentNumber;
}
}

View File

@@ -1,109 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.plugins.SegmentSource;
import net.sf.briar.api.transport.Segment;
class SegmentedIncomingEncryptionLayer implements IncomingEncryptionLayer {
private final SegmentSource in;
private final Cipher tagCipher, segCipher;
private final ErasableKey tagKey, segKey;
private final boolean tagEverySegment;
private final int blockSize, headerLength, maxSegmentLength;
private final Segment segment;
private final byte[] iv;
private Segment bufferedSegment;
private boolean firstSegment = true;
private long segmentNumber = 0L;
SegmentedIncomingEncryptionLayer(SegmentSource in, Cipher tagCipher,
Cipher segCipher, ErasableKey tagKey, ErasableKey segKey,
boolean tagEverySegment, boolean ackHeader,
Segment bufferedSegment) {
this.in = in;
this.tagCipher = tagCipher;
this.segCipher = segCipher;
this.tagKey = tagKey;
this.segKey = segKey;
this.tagEverySegment = tagEverySegment;
this.bufferedSegment = bufferedSegment;
blockSize = segCipher.getBlockSize();
if(blockSize < FRAME_HEADER_LENGTH)
throw new IllegalArgumentException();
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
int length = in.getMaxSegmentLength();
if(length < TAG_LENGTH + headerLength + 1 + MAC_LENGTH)
throw new IllegalArgumentException();
if(length > MAX_SEGMENT_LENGTH) throw new IllegalArgumentException();
maxSegmentLength = length - TAG_LENGTH;
segment = new SegmentImpl(length);
iv = IvEncoder.encodeIv(0L, blockSize);
}
public boolean readSegment(Segment s) throws IOException,
InvalidDataException {
boolean expectTag = tagEverySegment || firstSegment;
firstSegment = false;
try {
// Read the segment, unless we have one buffered
Segment segment;
if(bufferedSegment == null) {
segment = this.segment;
if(!in.readSegment(segment)) return false;
} else {
segment = bufferedSegment;
bufferedSegment = null;
}
int offset = expectTag ? TAG_LENGTH : 0;
int length = segment.getLength();
if(length < offset + headerLength + MAC_LENGTH)
throw new InvalidDataException();
if(length > offset + maxSegmentLength)
throw new InvalidDataException();
byte[] ciphertext = segment.getBuffer();
// If a tag is expected then decrypt and validate it
if(expectTag) {
long seg = TagEncoder.decodeTag(ciphertext, tagCipher, tagKey);
if(seg == -1) throw new InvalidDataException();
segmentNumber = seg;
}
// Decrypt the segment
try {
IvEncoder.updateIv(iv, segmentNumber);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
segCipher.init(Cipher.DECRYPT_MODE, segKey, ivSpec);
int decrypted = segCipher.doFinal(ciphertext, offset,
length - offset, s.getBuffer());
if(decrypted != length - offset) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
s.setLength(length - offset);
s.setSegmentNumber(segmentNumber++);
return true;
} catch(IOException e) {
segKey.erase();
tagKey.erase();
throw e;
}
}
public int getMaxSegmentLength() {
return maxSegmentLength;
}
}

View File

@@ -1,91 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_SEGMENT_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.api.plugins.SegmentSink;
import net.sf.briar.api.transport.Segment;
class SegmentedOutgoingEncryptionLayer implements OutgoingEncryptionLayer {
private final SegmentSink out;
private final Cipher tagCipher, segCipher;
private final ErasableKey tagKey, segKey;
private final boolean tagEverySegment;
private final int headerLength, maxSegmentLength;
private final Segment segment;
private final byte[] iv;
private long capacity;
SegmentedOutgoingEncryptionLayer(SegmentSink out, long capacity,
Cipher tagCipher, Cipher segCipher, ErasableKey tagKey,
ErasableKey segKey, boolean tagEverySegment, boolean ackHeader) {
this.out = out;
this.capacity = capacity;
this.tagCipher = tagCipher;
this.segCipher = segCipher;
this.tagKey = tagKey;
this.segKey = segKey;
this.tagEverySegment = tagEverySegment;
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
int length = out.getMaxSegmentLength();
if(length < TAG_LENGTH + headerLength + 1 + MAC_LENGTH)
throw new IllegalArgumentException();
if(length > MAX_SEGMENT_LENGTH) throw new IllegalArgumentException();
maxSegmentLength = length - MAC_LENGTH;
segment = new SegmentImpl(length);
iv = IvEncoder.encodeIv(0L, segCipher.getBlockSize());
}
public void writeSegment(Segment s) throws IOException {
byte[] plaintext = s.getBuffer(), ciphertext = segment.getBuffer();
int length = s.getLength();
long segmentNumber = s.getSegmentNumber();
int offset = 0;
if(tagEverySegment || segmentNumber == 0) {
TagEncoder.encodeTag(ciphertext, segmentNumber, tagCipher, tagKey);
offset = TAG_LENGTH;
}
IvEncoder.updateIv(iv, segmentNumber);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
try {
segCipher.init(Cipher.ENCRYPT_MODE, segKey, ivSpec);
int encrypted = segCipher.doFinal(plaintext, 0, length,
ciphertext, offset);
if(encrypted != length) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
segment.setLength(offset + length);
try {
out.writeSegment(segment);
} catch(IOException e) {
segKey.erase();
tagKey.erase();
throw e;
}
capacity -= offset + length;
}
public void flush() throws IOException {}
public long getRemainingCapacity() {
return capacity;
}
public int getMaxSegmentLength() {
return maxSegmentLength;
}
}

View File

@@ -1,26 +1,19 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.util.ByteUtils;
class TagEncoder {
static void encodeTag(byte[] tag, long segmentNumber, Cipher tagCipher,
ErasableKey tagKey) {
static void encodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if(segmentNumber < 0 || segmentNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Clear the tag
// Blank plaintext
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
// Encode the segment number as a uint32 at the end of the tag
ByteUtils.writeUint32(segmentNumber, tag, TAG_LENGTH - 4);
try {
tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
int encrypted = tagCipher.doFinal(tag, 0, TAG_LENGTH, tag);
@@ -31,18 +24,18 @@ class TagEncoder {
}
}
static long decodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey) {
static boolean decodeTag(byte[] tag, Cipher tagCipher, ErasableKey tagKey) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
try {
tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
byte[] plaintext = tagCipher.doFinal(tag, 0, TAG_LENGTH);
if(plaintext.length != TAG_LENGTH)
throw new IllegalArgumentException();
// All but the last four bytes of the plaintext should be blank
for(int i = 0; i < TAG_LENGTH - 4; i++) {
if(plaintext[i] != 0) return -1;
//The plaintext should be blank
for(int i = 0; i < TAG_LENGTH; i++) {
if(plaintext[i] != 0) return false;
}
return ByteUtils.readUint32(plaintext, TAG_LENGTH - 4);
return true;
} catch(GeneralSecurityException e) {
// Unsuitable cipher or key
throw new IllegalArgumentException(e);

View File

@@ -1,75 +0,0 @@
package net.sf.briar.transport;
import static net.sf.briar.api.transport.TransportConstants.ACK_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAC_LENGTH;
import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.transport.Segment;
/** An erasure decoder that uses k data segments and one parity segment. */
class XorErasureDecoder implements ErasureDecoder {
private final int n, headerLength;
XorErasureDecoder(int n, boolean ackHeader) {
this.n = n;
if(ackHeader) headerLength = FRAME_HEADER_LENGTH + ACK_HEADER_LENGTH;
else headerLength = FRAME_HEADER_LENGTH;
}
public boolean decodeFrame(Frame f, Segment[] set) throws FormatException {
// We need at least n - 1 pieces
int pieces = 0;
for(int i = 0; i < n; i++) if(set[i] != null) pieces++;
if(pieces < n - 1) return false;
// All the pieces must have the same length - take the minimum
int length = MAX_FRAME_LENGTH;
for(int i = 0; i < n; i++) {
if(set[i] != null) {
int len = set[i].getLength();
if(len < length) length = len;
}
}
if(length * (n - 1) > MAX_FRAME_LENGTH) throw new FormatException();
// Decode the frame
byte[] dest = f.getBuffer();
int offset = 0;
if(pieces == n || set[n - 1] == null) {
// We don't need no stinkin' parity segment
for(int i = 0; i < n - 1; i++) {
byte[] src = set[i].getBuffer();
int copyLength = Math.min(length, dest.length - offset);
System.arraycopy(src, 0, dest, offset, copyLength);
offset += length;
}
} else {
// Reconstruct the missing segment
byte[] parity = new byte[length];
int missingOffset = -1;
for(int i = 0; i < n - 1; i++) {
if(set[i] == null) {
missingOffset = offset;
} else {
byte[] src = set[i].getBuffer();
for(int j = 0; j < length; j++) parity[j] ^= src[j];
int copyLength = Math.min(length, dest.length - offset);
System.arraycopy(src, 0, dest, offset, copyLength);
}
offset += length;
}
byte[] src = set[n - 1].getBuffer();
for(int i = 0; i < length; i++) parity[i] ^= src[i];
assert missingOffset != -1;
int copyLength = Math.min(length, dest.length - missingOffset);
System.arraycopy(parity, 0, dest, missingOffset, copyLength);
}
// The frame length might not be an exact multiple of the segment length
int payload = HeaderEncoder.getPayloadLength(dest);
int padding = HeaderEncoder.getPaddingLength(dest);
int frameLength = headerLength + payload + padding + MAC_LENGTH;
if(frameLength > MAX_FRAME_LENGTH) throw new FormatException();
f.setLength(frameLength);
return true;
}
}

View File

@@ -1,31 +0,0 @@
package net.sf.briar.transport;
import net.sf.briar.api.transport.Segment;
/** An erasure encoder than uses k data segments and one parity segment. */
class XorErasureEncoder implements ErasureEncoder {
private final int n;
XorErasureEncoder(int n) {
this.n = n;
}
public Segment[] encodeFrame(Frame f) {
Segment[] set = new Segment[n];
int length = (int) Math.ceil((float) f.getLength() / (n - 1));
for(int i = 0; i < n; i++) {
set[i] = new SegmentImpl(length);
set[i].setLength(length);
}
byte[] src = f.getBuffer(), parity = set[n - 1].getBuffer();
int offset = 0;
for(int i = 0; i < n - 1; i++) {
int copyLength = Math.min(length, src.length - offset);
System.arraycopy(src, offset, set[i].getBuffer(), 0, copyLength);
for(int j = 0; j < copyLength; j++) parity[j] ^= src[offset + j];
offset += length;
}
return set;
}
}