diff --git a/components/net/sf/briar/transport/ErasureDecoder.java b/components/net/sf/briar/transport/ErasureDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..679c26ab20f19ffa35a17bafdd510488c8485b11 --- /dev/null +++ b/components/net/sf/briar/transport/ErasureDecoder.java @@ -0,0 +1,13 @@ +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; +} diff --git a/components/net/sf/briar/transport/FrameWindowImpl.java b/components/net/sf/briar/transport/FrameWindowImpl.java index ddf3e71cf65793dca4c97199dba7bcbfc09afd9f..c3ff67a75a789c42d15fa8528f0f682d7c391c5a 100644 --- a/components/net/sf/briar/transport/FrameWindowImpl.java +++ b/components/net/sf/briar/transport/FrameWindowImpl.java @@ -6,6 +6,7 @@ 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; diff --git a/components/net/sf/briar/transport/IncomingErrorCorrectionLayerImpl.java b/components/net/sf/briar/transport/IncomingErrorCorrectionLayerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..db3ab691d2713d6470bf11550a6fbf387a182a00 --- /dev/null +++ b/components/net/sf/briar/transport/IncomingErrorCorrectionLayerImpl.java @@ -0,0 +1,68 @@ +package net.sf.briar.transport; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +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; + private final Map<Long, Integer> discardCounts; + private final Map<Long, Segment[]> segmentSets; + + IncomingErrorCorrectionLayerImpl(IncomingEncryptionLayer in, + ErasureDecoder decoder, int n, int k) { + this.in = in; + this.decoder = decoder; + this.n = n; + this.k = k; + discardCounts = new HashMap<Long, Integer>(); + segmentSets = new HashMap<Long, Segment[]>(); + } + + public boolean readFrame(Frame f, FrameWindow window) throws IOException, + InvalidDataException { + // Free any segment sets that have been removed from the window + Iterator<Long> it = segmentSets.keySet().iterator(); + while(it.hasNext()) if(!window.contains(it.next())) it.remove(); + // 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(); + // Allocate a segment + Segment s = new SegmentImpl(); + // 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)) return false; + frameNumber = s.getSegmentNumber() / n; + if(window.contains(frameNumber)) break; + if(window.isTooHigh(frameNumber)) countDiscard(frameNumber); + } + // Add the segment to its segment set, or create one if necessary + Segment[] set = segmentSets.get(frameNumber); + if(set == null) { + set = new Segment[n]; + segmentSets.put(frameNumber, set); + } else { + set[(int) (frameNumber % n)] = s; + } + // Try to decode the frame + if(decoder.decodeFrame(f, set)) return true; + } + } + + 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); + } +} diff --git a/components/net/sf/briar/transport/NullFrameWindow.java b/components/net/sf/briar/transport/NullFrameWindow.java index c56a78a37dea818d2c0f01109d39d52c378f4b3f..9f5fb19f7145b800fb871b31b5460472fd5c8621 100644 --- a/components/net/sf/briar/transport/NullFrameWindow.java +++ b/components/net/sf/briar/transport/NullFrameWindow.java @@ -2,6 +2,7 @@ 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; diff --git a/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java b/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java index 87ce8d4b358ee0bb4d2dc76b248fc8a4d9e37c69..54e1f04aec8821e127bc7ae4486da58cc6bed18b 100644 --- a/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java +++ b/components/net/sf/briar/transport/NullIncomingErrorCorrectionLayer.java @@ -14,12 +14,13 @@ class NullIncomingErrorCorrectionLayer implements IncomingErrorCorrectionLayer { segment = new SegmentImpl(); } - public boolean readFrame(Frame f, FrameWindow window) - throws IOException, InvalidDataException { + public boolean readFrame(Frame f, FrameWindow window) throws IOException, + InvalidDataException { while(true) { if(!in.readSegment(segment)) return false; byte[] buf = segment.getBuffer(); - if(window.contains(HeaderEncoder.getFrameNumber(buf))) break; + long frameNumber = HeaderEncoder.getFrameNumber(buf); + if(window.contains(frameNumber)) break; } int length = segment.getLength(); // FIXME: Unnecessary copy diff --git a/components/net/sf/briar/transport/XorErasureDecoder.java b/components/net/sf/briar/transport/XorErasureDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..61682185a1673f76f575e50707e6e2f86dd186f5 --- /dev/null +++ b/components/net/sf/briar/transport/XorErasureDecoder.java @@ -0,0 +1,60 @@ +package net.sf.briar.transport; + +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; + + XorErasureDecoder(int n) { + this.n = n; + } + + 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(); + System.arraycopy(src, 0, dest, offset, length); + offset += length; + } + } else { + // Reconstruct the missing segment + byte[] parity = new byte[length]; + int missingOffset = -1; + for(int i = 0; i < n; i++) { + if(set[i] == null) { + missingOffset = offset; + } else { + byte[] src = set[i].getBuffer(); + System.arraycopy(src, 0, dest, offset, length); + for(int j = 0; j < length; j++) parity[j] ^= src[j]; + } + offset += length; + } + assert missingOffset != -1; + System.arraycopy(parity, 0, dest, missingOffset, length); + } + f.setLength(offset); + return true; + } +}