diff --git a/components/net/sf/briar/transport/Frame.java b/components/net/sf/briar/transport/Frame.java index f9d3ca2d1ba346941847556dee5e8a08f9049bcf..64c8eeda9a94cf71b7752bde3c0498f3395742de 100644 --- a/components/net/sf/briar/transport/Frame.java +++ b/components/net/sf/briar/transport/Frame.java @@ -4,10 +4,18 @@ import static net.sf.briar.api.transport.TransportConstants.MAX_FRAME_LENGTH; class Frame { - private final byte[] buf = new byte[MAX_FRAME_LENGTH]; + private final byte[] buf; private int length = -1; + Frame() { + this(MAX_FRAME_LENGTH); + } + + Frame(int length) { + buf = new byte[length]; + } + public byte[] getBuffer() { return buf; } diff --git a/components/net/sf/briar/transport/XorErasureDecoder.java b/components/net/sf/briar/transport/XorErasureDecoder.java index 47093034edd670874fa4211cd6f83008194b9245..685aab252e74842a5fb87cf89e463e40de05e2ad 100644 --- a/components/net/sf/briar/transport/XorErasureDecoder.java +++ b/components/net/sf/briar/transport/XorErasureDecoder.java @@ -36,27 +36,31 @@ class XorErasureDecoder implements ErasureDecoder { // 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); + 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; i++) { + for(int i = 0; i < n - 1; 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]; + 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; - System.arraycopy(parity, 0, dest, missingOffset, length); + int copyLength = Math.min(length, dest.length - missingOffset); + System.arraycopy(parity, 0, dest, missingOffset, copyLength); } - assert offset == length * (n - 1); // The frame length may not be an exact multiple of the segment length int payload = HeaderEncoder.getPayloadLength(dest); int padding = HeaderEncoder.getPaddingLength(dest); diff --git a/components/net/sf/briar/transport/XorErasureEncoder.java b/components/net/sf/briar/transport/XorErasureEncoder.java index b0a529b6473c538d6069c6f1e8e0b348e0f48029..220f679e38b92968cb902a5a709944430a05402b 100644 --- a/components/net/sf/briar/transport/XorErasureEncoder.java +++ b/components/net/sf/briar/transport/XorErasureEncoder.java @@ -21,8 +21,9 @@ class XorErasureEncoder implements ErasureEncoder { byte[] src = f.getBuffer(), parity = set[n - 1].getBuffer(); int offset = 0; for(int i = 0; i < n - 1; i++) { - System.arraycopy(src, offset, set[i].getBuffer(), 0, length); - for(int j = 0; j < length; j++) parity[j] ^= src[offset + j]; + 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; diff --git a/test/build.xml b/test/build.xml index 2f41b5e64e9f118f1c81d9ce4481405acb5fab46..c2b2dd641e8c53764d2e97334b80dc86c7653a24 100644 --- a/test/build.xml +++ b/test/build.xml @@ -61,6 +61,7 @@ <test name='net.sf.briar.transport.IncomingSegmentedEncryptionLayerTest'/> <test name='net.sf.briar.transport.OutgoingEncryptionLayerImplTest'/> <test name='net.sf.briar.transport.OutgoingSegmentedEncryptionLayerTest'/> + <test name='net.sf.briar.transport.XorErasureCodeTest'/> <test name='net.sf.briar.transport.XorErasureDecoderTest'/> <test name='net.sf.briar.transport.XorErasureEncoderTest'/> <test name='net.sf.briar.util.ByteUtilsTest'/> diff --git a/test/net/sf/briar/transport/XorErasureCodeTest.java b/test/net/sf/briar/transport/XorErasureCodeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b361cc2d83371fbaceb9fc4d39a83a0c14dca175 --- /dev/null +++ b/test/net/sf/briar/transport/XorErasureCodeTest.java @@ -0,0 +1,50 @@ +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 java.util.Random; + +import net.sf.briar.BriarTestCase; +import net.sf.briar.api.transport.Segment; + +import static org.junit.Assert.assertArrayEquals; +import org.junit.Test; + +public class XorErasureCodeTest extends BriarTestCase { + + @Test + public void testEncodingAndDecodingWithAllSegments() throws Exception { + XorErasureEncoder e = new XorErasureEncoder(5); + XorErasureDecoder d = new XorErasureDecoder(5); + Frame f = new Frame(1234); + new Random().nextBytes(f.getBuffer()); + int payload = 1234 - FRAME_HEADER_LENGTH - MAC_LENGTH; + HeaderEncoder.encodeHeader(f.getBuffer(), 0L, payload, 0); + f.setLength(1234); + Segment[] set = e.encodeFrame(f); + assertEquals(5, set.length); + Frame f1 = new Frame(1234); + assertTrue(d.decodeFrame(f1, set)); + assertArrayEquals(f.getBuffer(), f1.getBuffer()); + } + + @Test + public void testEncodingAndDecodingWithMissingSegment() throws Exception { + XorErasureEncoder e = new XorErasureEncoder(5); + XorErasureDecoder d = new XorErasureDecoder(5); + Frame f = new Frame(1234); + new Random().nextBytes(f.getBuffer()); + int payload = 1234 - FRAME_HEADER_LENGTH - MAC_LENGTH; + HeaderEncoder.encodeHeader(f.getBuffer(), 0L, payload, 0); + f.setLength(1234); + for(int i = 0; i < 5; i++) { + Segment[] set = e.encodeFrame(f); + assertEquals(5, set.length); + set[i] = null; + Frame f1 = new Frame(1234); + assertTrue(d.decodeFrame(f1, set)); + assertArrayEquals(f.getBuffer(), f1.getBuffer()); + } + } +} diff --git a/test/net/sf/briar/transport/XorErasureDecoderTest.java b/test/net/sf/briar/transport/XorErasureDecoderTest.java index 3b78f755b497a1a4dd0c2f19794f94dda368eb8f..944610fcbeae3700f9ef429410095d0a8efa4480 100644 --- a/test/net/sf/briar/transport/XorErasureDecoderTest.java +++ b/test/net/sf/briar/transport/XorErasureDecoderTest.java @@ -14,24 +14,15 @@ public class XorErasureDecoderTest extends BriarTestCase { @Test public void testMaximumLength() throws Exception { + XorErasureDecoder d = new XorErasureDecoder(5); // A frame of the maximum length should be decoded successfully Segment[] set = encodeEmptyFrame(MAX_FRAME_LENGTH / 4, 5); - XorErasureDecoder d = new XorErasureDecoder(5); Frame f = new Frame(); assertTrue(d.decodeFrame(f, set)); - // Check the header - byte[] b = f.getBuffer(); - assertEquals(0L, HeaderEncoder.getFrameNumber(b)); - int payload = MAX_FRAME_LENGTH - FRAME_HEADER_LENGTH - MAC_LENGTH; - assertEquals(payload, HeaderEncoder.getPayloadLength(b)); - assertEquals(0, HeaderEncoder.getPaddingLength(b)); - // Check the body - assertEquals(MAX_FRAME_LENGTH, f.getLength()); - for(int i = FRAME_HEADER_LENGTH; i < MAX_FRAME_LENGTH; i++) { - assertEquals(0, b[i]); - } + checkFrame(f, MAX_FRAME_LENGTH); // A frame larger than the maximum length should not be decoded set = encodeEmptyFrame(MAX_FRAME_LENGTH / 4 + 1, 5); + f = new Frame(); try { d.decodeFrame(f, set); } catch(FormatException expected) {} @@ -47,12 +38,35 @@ public class XorErasureDecoderTest extends BriarTestCase { set[1].setLength(251); // The frame should be decoded successfully XorErasureDecoder d = new XorErasureDecoder(4); - Frame f = new Frame(); + Frame f = new Frame(750); assertTrue(d.decodeFrame(f, set)); // The minimum of the segments' lengths should have been used assertEquals(750, f.getLength()); } + @Test + public void testDecodingWithMissingSegment() throws Exception { + XorErasureDecoder d = new XorErasureDecoder(4); + for(int i = 0; i < 4; i++) { + Segment[] set = encodeEmptyFrame(250, 4); + set[i] = null; + // The frame should be decoded successfully + Frame f = new Frame(750); + assertTrue(d.decodeFrame(f, set)); + checkFrame(f, 750); + } + } + + @Test + public void testDecodingWithTwoMissingSegments() throws Exception { + XorErasureDecoder d = new XorErasureDecoder(4); + Segment[] set = encodeEmptyFrame(250, 4); + set[0] = null; + set[1] = null; + Frame f = new Frame(750); + assertFalse(d.decodeFrame(f, set)); + } + private Segment[] encodeEmptyFrame(int length, int n) { Segment[] set = new Segment[n]; for(int i = 0; i < n; i++) { @@ -64,4 +78,17 @@ public class XorErasureDecoderTest extends BriarTestCase { HeaderEncoder.encodeHeader(set[n - 1].getBuffer(), 0L, payload, 0); return set; } + + private void checkFrame(Frame f, int length) { + byte[] b = f.getBuffer(); + assertEquals(0L, HeaderEncoder.getFrameNumber(b)); + int payload = length - FRAME_HEADER_LENGTH - MAC_LENGTH; + assertEquals(payload, HeaderEncoder.getPayloadLength(b)); + assertEquals(0, HeaderEncoder.getPaddingLength(b)); + // Check the body + assertEquals(length, f.getLength()); + for(int i = FRAME_HEADER_LENGTH; i < length; i++) { + assertEquals("" + i, 0, b[i]); + } + } }