package freenet.client; import java.util.Arrays; import java.util.Random; import freenet.support.TestProperty; import junit.framework.Assert; import junit.framework.TestCase; /** Test the new (post db4o) high level FEC API */ public class OnionFECCodecTest extends TestCase { private static final int BLOCK_SIZE = 4096; private static final int MAX_SEGMENT_SIZE = 255; private final OnionFECCodec codec = new OnionFECCodec(); private byte[][] originalDataBlocks; private byte[][] dataBlocks; private byte[][] originalCheckBlocks; private byte[][] checkBlocks; private boolean[] checkBlocksPresent; private boolean[] dataBlocksPresent; public void testDecodeRandomSubset() { Random r = new Random(19412106); int iterations = TestProperty.EXTENSIVE ? 100 : 10; for(int i=0;i<iterations;i++) inner(128, 128, r); for(int i=0;i<iterations;i++) inner(127, 129, r); for(int i=0;i<iterations;i++) inner(129, 127, r); } public void testEncodeThrowsOnNotPaddedLastBlock() { Random r = new Random(21502106); int data = 128; int check = 128; originalDataBlocks = createOriginalDataBlocks(r, data); originalDataBlocks[data-1] = new byte[BLOCK_SIZE/2]; checkBlocks = setupCheckBlocks(check); dataBlocks = copy(originalDataBlocks); // Encode the check blocks. checkBlocksPresent = new boolean[checkBlocks.length]; try { codec.encode(dataBlocks, checkBlocks, checkBlocksPresent, BLOCK_SIZE); assertTrue(false); // Should throw here! } catch (IllegalArgumentException e) { // Expected. } } public void testDecodeThrowsOnNotPaddedLastBlock() { Random r = new Random(21482106); setup(128, 128, r); // Now delete a random selection of blocks deleteRandomBlocks(r); dataBlocks[127] = new byte[BLOCK_SIZE/2]; try { codec.decode(dataBlocks, checkBlocks, dataBlocksPresent, checkBlocksPresent, BLOCK_SIZE); assertTrue(false); // Should throw } catch (IllegalArgumentException e) { // Ok. } } public void testDecodeAlreadyDecoded() { Random r = new Random(21482106); setup(128, 128, r); // Now delete a random selection of blocks deleteAllCheckBlocks(); decode(); // Should be a no-op. } public void testDecodeNoneDecoded() { Random r = new Random(21482106); setup(128, 128, r); // Now delete a random selection of blocks deleteAllDataBlocks(); decode(); } public void testManyCheckFewData() { Random r = new Random(21582106); inner(2, 253, r); inner(5, 250, r); inner(50, 200, r); inner(2, 3, r); // Common case, include it here. } public void testManyDataFewCheck() { Random r = new Random(21592106); inner(200, 55, r); inner(253, 2, r); } public void testRandomDataCheckCounts() { Random r = new Random(21602106); int iterations = TestProperty.EXTENSIVE ? 100 : 10; for(int i=0;i<iterations;i++) { int data = r.nextInt(252)+2; int maxCheck = 255 - data; int check = r.nextInt(maxCheck)+1; inner(data, check, r); } } protected void inner(int data, int check, Random r) { setup(data, check, r); // Now delete a random selection of blocks deleteRandomBlocks(r); decode(); } protected void setup(int data, int check, Random r) { originalDataBlocks = createOriginalDataBlocks(r, data); checkBlocks = setupCheckBlocks(check); dataBlocks = copy(originalDataBlocks); // Encode the check blocks. checkBlocksPresent = new boolean[checkBlocks.length]; codec.encode(dataBlocks, checkBlocks, checkBlocksPresent, BLOCK_SIZE); assertEquals(originalDataBlocks, dataBlocks); originalCheckBlocks = copy(checkBlocks); // Initially everything is present... dataBlocksPresent = new boolean[dataBlocks.length]; for(int i=0;i<dataBlocksPresent.length;i++) dataBlocksPresent[i] = true; for(int i=0;i<checkBlocksPresent.length;i++) checkBlocksPresent[i] = true; } protected void decode() { boolean[] oldDataBlocksPresent = dataBlocksPresent.clone(); boolean[] oldCheckBlocksPresent = checkBlocksPresent.clone(); codec.decode(dataBlocks, checkBlocks, dataBlocksPresent, checkBlocksPresent, BLOCK_SIZE); assertEquals(originalDataBlocks, dataBlocks); assertTrue(Arrays.equals(oldDataBlocksPresent, dataBlocksPresent)); assertTrue(Arrays.equals(oldCheckBlocksPresent, checkBlocksPresent)); for(int i=0;i<dataBlocksPresent.length;i++) dataBlocksPresent[i] = true; codec.encode(dataBlocks, checkBlocks, checkBlocksPresent, BLOCK_SIZE); assertEquals(originalCheckBlocks, checkBlocks); assertTrue(Arrays.equals(oldCheckBlocksPresent, checkBlocksPresent)); } private void deleteRandomBlocks(Random r) { int dropped = 0; int data = dataBlocks.length; int check = checkBlocks.length; while(dropped < check) { int blockNo = r.nextInt(data+check); if(blockNo < data) { if(!dataBlocksPresent[blockNo]) continue; clear(dataBlocks, blockNo); dataBlocksPresent[blockNo] = false; } else { blockNo -= data; if(!checkBlocksPresent[blockNo]) continue; clear(checkBlocks, blockNo); checkBlocksPresent[blockNo] = false; } dropped++; } } private void clear(byte[][] dataBlocks, int blockNo) { Arrays.fill(dataBlocks[blockNo], (byte)0); } protected byte[][] createOriginalDataBlocks(Random r, int count) { byte[][] blocks = new byte[count][]; for(int i=0;i<count;i++) { blocks[i] = new byte[BLOCK_SIZE]; r.nextBytes(blocks[i]); } return blocks; } protected byte[][] setupCheckBlocks(int count) { byte[][] blocks = new byte[count][]; for(int i=0;i<count;i++) { blocks[i] = new byte[BLOCK_SIZE]; } return blocks; } protected byte[][] copy(byte[][] blocks) { byte[][] ret = new byte[blocks.length][]; // FIXME would blocks.clone() shallow or deep copy? for(int i=0;i<ret.length;i++) { ret[i] = blocks[i].clone(); } return ret; } private void assertEquals(byte[][] blocks1, byte[][] blocks2) { assertEquals(blocks1.length, blocks2.length); for(int i=0;i<blocks1.length;i++) { assertTrue(Arrays.equals(blocks1[i], blocks2[i])); } } private void deleteAllDataBlocks() { for(int i=0;i<dataBlocks.length;i++) { clear(dataBlocks, i); dataBlocksPresent[i] = false; } } private void deleteAllCheckBlocks() { for(int i=0;i<checkBlocks.length;i++) { clear(checkBlocks, i); checkBlocksPresent[i] = false; } } }