package com.limegroup.gnutella.routing; import java.io.ByteArrayOutputStream; import java.util.Arrays; import java.util.Iterator; import java.util.Random; import java.util.zip.Inflater; import junit.framework.Test; import com.limegroup.gnutella.HugeTestUtils; import com.limegroup.gnutella.messages.BadPacketException; import com.limegroup.gnutella.messages.QueryRequest; import com.limegroup.gnutella.util.IOUtils; import com.limegroup.gnutella.util.BitSet; import com.limegroup.gnutella.util.PrivilegedAccessor; public class QueryRouteTableTest extends com.limegroup.gnutella.util.BaseTestCase { public QueryRouteTableTest(String name) { super(name); } public static Test suite() { return buildTestSuite(QueryRouteTableTest.class); } public void assertTrue(boolean test, String out) { assertTrue(out, test); } /** May return null! */ private BitSet getBitTable(QueryRouteTable source) throws Exception { BitSet retSet = null; retSet = (BitSet) PrivilegedAccessor.getValue(source, "bitTable"); return retSet; } /** If it can't get anything, will return 0. */ private int getBitTableLength(QueryRouteTable source) throws Exception { int retLength = 0; Integer intObj = (Integer)PrivilegedAccessor.getValue(source, "bitTableLength"); retLength = intObj.intValue(); return retLength; } /** If it can't get anything, will return 0. */ private void setUncompressor(QueryRouteTable source, Inflater inflater) throws Exception { PrivilegedAccessor.setValue(source, "uncompressor", inflater); } private byte[] invokeCompress(QueryRouteTable source, byte[] chunk) throws Exception { byte[] retBytes = new byte[0]; retBytes = IOUtils.deflate(chunk); return retBytes; } private byte[] invokeUncompress(QueryRouteTable source, byte[] chunk) throws Exception { byte[] retBytes = new byte[0]; retBytes = (byte[]) PrivilegedAccessor.invokeMethod(source, "uncompress", chunk); return retBytes; } private byte[] invokeHalve(byte[] chunk) throws Exception { byte[] retBytes = new byte[0]; retBytes = (byte[]) PrivilegedAccessor.invokeMethod(QueryRouteTable.class, "halve", chunk); return retBytes; } private byte[] invokeUnhalve(byte[] chunk) throws Exception { byte[] retBytes = new byte[0]; retBytes = (byte[]) PrivilegedAccessor.invokeMethod(QueryRouteTable.class, "unhalve", chunk); return retBytes; } private int entries(QueryRouteTable tbl) throws Exception{ BitSet set = getBitTable(tbl); return set.cardinality(); } public void testCompressionAndUncompress() throws Exception { //0. compress/uncompress. First we make a huge array with lots of //random bytes but also long strings of zeroes. This means that //compression will work, but not too well. Then we take the compressed //value and dice it up randomly. It's critical to make sure that //decompress works incrementally without blocking. QueryRouteTable dummy=new QueryRouteTable(); setUncompressor(dummy, new Inflater()); byte[] data=new byte[10000]; Random rand=new Random(); rand.nextBytes(data); for (int i=100; i<7000; i++) { data[i]=(byte)0; } byte[] dataCompressed=invokeCompress(dummy, data); assertLessThan(data.length, dataCompressed.length); ByteArrayOutputStream baos=new ByteArrayOutputStream(); for (int i=0; i<dataCompressed.length; ) { int length=Math.min(rand.nextInt(100), dataCompressed.length-i); byte[] chunk=new byte[length]; System.arraycopy(dataCompressed, i, chunk, 0, length); byte[] chunkRead=invokeUncompress(dummy, chunk); baos.write(chunkRead); i+=length; } baos.flush(); assertTrue(Arrays.equals(data, baos.toByteArray()), "Compress/uncompress loop failed"); } public void testHalveAndUnhalve() throws Exception { //0.1. halve/unhalve assertEquals(0x03, QueryRouteTable.extendNibble((byte)0x03)); assertEquals((byte)0xF9, QueryRouteTable.extendNibble((byte)0x09)); byte[] big={(byte)1, (byte)7, (byte)-1, (byte)-8}; byte[] small={(byte)0x17, (byte)0xF8}; assertTrue(Arrays.equals(QueryRouteTable.halve(big), small)); assertTrue(Arrays.equals(QueryRouteTable.unhalve(small), big)); } public void testEntries() throws Exception { QueryRouteTable qrt=new QueryRouteTable(1000); qrt.add("good book"); assertEquals(2,entries(qrt)); qrt.add("bad"); //{good, book, bad} assertEquals(3,entries(qrt)); qrt.add("bad"); //{good, book, bad} assertEquals(3,entries(qrt)); //{good, book, bad, SHA1} qrt.addIndivisible(HugeTestUtils.UNIQUE_SHA1.toString()); assertEquals(4,entries(qrt)); //1. Simple keyword tests (add, contains) //we have moved to 1-bit entry per hash, so either absent or present.... assertTrue(! qrt.contains( QueryRequest.createQuery("garbage",(byte)4))); assertTrue(qrt.contains(QueryRequest.createQuery("bad", (byte)2))); assertTrue(qrt.contains(QueryRequest.createQuery("bad", (byte)3))); assertTrue(qrt.contains(QueryRequest.createQuery("bad", (byte)4))); assertTrue(qrt.contains( QueryRequest.createQuery("good bad", (byte)2))); assertTrue(! qrt.contains( QueryRequest.createQuery("good bd", (byte)3))); assertTrue(qrt.contains(QueryRequest.createQuery( "good bad book", (byte)3))); assertTrue(! qrt.contains(QueryRequest.createQuery( "good bad bok", (byte)3))); assertTrue(qrt.contains( QueryRequest.createQuery(HugeTestUtils.UNIQUE_SHA1))); } public void testAddAll() throws Exception { // set up initial qrt. QueryRouteTable qrt = new QueryRouteTable(1000); qrt.add("good book"); qrt.add("bad"); qrt.addIndivisible(HugeTestUtils.UNIQUE_SHA1.toString()); //2. addAll tests QueryRouteTable qrt2=new QueryRouteTable(1000); assertEquals(0,entries(qrt2)); qrt2.add("new"); qrt2.add("book"); qrt2.addAll(qrt); //{book, good, new, bad, SHA1} assertEquals(5, entries(qrt2)); QueryRouteTable qrt3=new QueryRouteTable(1000); qrt3.add("book"); qrt3.add("good"); qrt3.add("new"); qrt3.add("bad"); qrt3.addIndivisible(HugeTestUtils.UNIQUE_SHA1.toString()); assertEquals(qrt2,qrt3); assertEquals(qrt3,qrt2); } public void testEncodeAndDecode() throws Exception { // set up initial qrt. QueryRouteTable qrt = new QueryRouteTable(1000); qrt.add("good book"); qrt.add("bad"); qrt.addIndivisible(HugeTestUtils.UNIQUE_SHA1.toString()); //3. encode-decode test--with compression //qrt={good, book, bad} QueryRouteTable qrt2=new QueryRouteTable(1000); for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) { RouteTableMessage m=(RouteTableMessage)iter.next(); if(m instanceof PatchTableMessage) { try { qrt2.patch((PatchTableMessage)m); } catch (BadPacketException e) { } } else { qrt2.reset((ResetTableMessage)m); } if (m instanceof PatchTableMessage) assertTrue(((PatchTableMessage)m).getCompressor() ==PatchTableMessage.COMPRESSOR_DEFLATE); } assertEquals(qrt2, qrt); qrt.add("bad"); qrt.add("other"); //qrt={good, book, bad, other} assertNotEquals(qrt2,(qrt)); for (Iterator iter=qrt.encode(qrt2).iterator(); iter.hasNext(); ) { RouteTableMessage m=(RouteTableMessage)iter.next(); if(m instanceof PatchTableMessage) { try { qrt2.patch((PatchTableMessage)m); } catch (BadPacketException e) { } } else { qrt2.reset((ResetTableMessage)m); } if (m instanceof PatchTableMessage) assertTrue(((PatchTableMessage)m).getCompressor() ==PatchTableMessage.COMPRESSOR_DEFLATE); } assertEquals(qrt2,qrt); assertEquals(entries(qrt),entries(qrt2)); Iterator iter=qrt2.encode(qrt).iterator(); assertTrue(! iter.hasNext()); //test optimization iter=(new QueryRouteTable(1000).encode(null).iterator()); //blank table assertInstanceof(ResetTableMessage.class, iter.next()); assertTrue(! iter.hasNext()); } public void testEncodeAndDecodeNoCompression() throws Exception { //4. encode-decode test--without compression. (We know compression //won't work because the table is very small and filled with random //bytes.) QueryRouteTable qrt=new QueryRouteTable(10); Random rand=new Random(); for (int i=0; i<getBitTableLength(qrt); i++) if (rand.nextBoolean()) getBitTable(qrt).set(i); getBitTable(qrt).set(0); QueryRouteTable qrt2=new QueryRouteTable(10); assertNotEquals(qrt2,qrt); for (Iterator iter=qrt.encode(qrt2).iterator(); iter.hasNext(); ) { RouteTableMessage m=(RouteTableMessage)iter.next(); if(m instanceof PatchTableMessage) { try { qrt2.patch((PatchTableMessage)m); } catch (BadPacketException e) { } } else { qrt2.reset((ResetTableMessage)m); } if (m instanceof PatchTableMessage) assertEquals(PatchTableMessage.COMPRESSOR_NONE, ((PatchTableMessage)m).getCompressor()); } assertEquals(qrt2,(qrt)); } public void testEncodeAndDecodeMultiplePatches() throws Exception { //4b. Encode/decode tests with multiple patched messages. QueryRouteTable qrt=new QueryRouteTable(5000); Random rand=new Random(); for (int i=0; i<getBitTableLength(qrt); i++) if (rand.nextBoolean()) getBitTable(qrt).set(i); QueryRouteTable qrt2=new QueryRouteTable(5000); assertNotEquals(qrt2,(qrt)); for (Iterator iter=qrt.encode(qrt2).iterator(); iter.hasNext(); ) { RouteTableMessage m=(RouteTableMessage)iter.next(); if(m instanceof PatchTableMessage) { try { qrt2.patch((PatchTableMessage)m); } catch (BadPacketException e) { } } else { qrt2.reset((ResetTableMessage)m); } } assertEquals(qrt2,(qrt)); //4c. Encode/decode tests with multiple patched messages, // and a different infinity. qrt = new QueryRouteTable(5000, (byte)15); rand=new Random(); for (int i=0; i<getBitTableLength(qrt); i++) if (rand.nextBoolean()) getBitTable(qrt).set(i); qrt2 = new QueryRouteTable(5000); assertNotEquals(qrt2,(qrt)); for (Iterator iter=qrt.encode(null).iterator(); iter.hasNext(); ) { RouteTableMessage m=(RouteTableMessage)iter.next(); if(m instanceof PatchTableMessage) { try { qrt2.patch((PatchTableMessage)m); } catch (BadPacketException e) { } } else { qrt2.reset((ResetTableMessage)m); } } assertEquals(qrt2,(qrt)); } public void testDifferentInfinities() throws Exception { //Simple test -- make sure different infinities don't change //equality. QueryRouteTable qrt = new QueryRouteTable(5000, (byte)7); qrt.add("good"); qrt.add("bad"); qrt.add("book"); QueryRouteTable qrt2 = new QueryRouteTable(5000, (byte)15); qrt2.add("good"); qrt2.add("bad"); qrt2.add("book"); assertEquals(qrt, qrt2); } public void testInterpolationAndExtrapolation() throws Exception { //5. Interpolation/extrapolation glass-box tests. Remember that +1 is //added to everything! QueryRouteTable qrt=new QueryRouteTable(4); // 1 4 5 X ==> 2 6 QueryRouteTable qrt2=new QueryRouteTable(2); getBitTable(qrt).set(0); getBitTable(qrt).set(1); getBitTable(qrt).set(2); getBitTable(qrt).clear(3); qrt2.addAll(qrt); assertTrue(getBitTable(qrt2).get(0)); assertTrue(getBitTable(qrt2).get(1)); //This also tests tables with different TTL problem. (The 6 is qrt //is interepreted as infinity in qrt2, not a 7.) qrt=new QueryRouteTable(2); // 1 X ==> 2 2 X X qrt2=new QueryRouteTable(4); getBitTable(qrt).set(0); getBitTable(qrt).clear(1); qrt2.addAll(qrt); assertTrue(getBitTable(qrt2).get(0)); assertTrue(getBitTable(qrt2).get(1)); assertTrue(!getBitTable(qrt2).get(2)); assertTrue(!getBitTable(qrt2).get(3)); qrt=new QueryRouteTable(4); // 1 2 4 X ==> 2 3 5 qrt2=new QueryRouteTable(3); getBitTable(qrt).set(0); getBitTable(qrt).set(1); getBitTable(qrt).set(2); getBitTable(qrt).clear(3); qrt2.addAll(qrt); assertTrue(getBitTable(qrt2).get(0)); assertTrue(getBitTable(qrt2).get(1)); assertTrue(getBitTable(qrt2).get(2)); assertEquals(3, entries(qrt2)); qrt=new QueryRouteTable(3); // 1 4 X ==> 2 2 5 X qrt2=new QueryRouteTable(4); getBitTable(qrt).set(0); getBitTable(qrt).set(1); getBitTable(qrt).clear(2); qrt2.addAll(qrt); assertTrue(getBitTable(qrt2).get(0)); assertTrue(getBitTable(qrt2).get(1)); assertTrue(getBitTable(qrt2).get(2)); assertTrue(!getBitTable(qrt2).get(3)); assertEquals(3, entries(qrt2)); qrt=new QueryRouteTable(100); qrt2=new QueryRouteTable(10); getBitTable(qrt).set(11); getBitTable(qrt).set(20); qrt2.addAll(qrt); assertTrue(getBitTable(qrt2).get(1)); assertTrue(getBitTable(qrt2).get(2)); assertEquals(2, entries(qrt2)); } public void testAddAllBlackBox() throws Exception { //5b. Black-box test for addAll. QueryRouteTable qrt=new QueryRouteTable(128); qrt.add("good book"); qrt.add("bad"); //{good/1, book/1, bad/3} QueryRouteTable qrt2=new QueryRouteTable(512); qrt2.addAll(qrt); assertTrue(qrt2.contains(QueryRequest.createQuery("bad", (byte)4))); assertTrue(qrt2.contains(QueryRequest.createQuery("good", (byte)4))); qrt2=new QueryRouteTable(32); qrt2.addAll(qrt); assertTrue(qrt2.contains(QueryRequest.createQuery("bad", (byte)4))); assertTrue(qrt2.contains(QueryRequest.createQuery("good", (byte)4))); } public void testBadPackets() throws Exception { //6. Test sequence numbers. QueryRouteTable qrt=new QueryRouteTable(); //a. wrong sequence after reset ResetTableMessage reset=null; PatchTableMessage patch=new PatchTableMessage((short)2, (short)2, PatchTableMessage.COMPRESSOR_DEFLATE, (byte)8, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown - expecting sequence number 1"); } catch (BadPacketException e) { } qrt=new QueryRouteTable(); //b. sequence sizes don't match reset=new ResetTableMessage(1024, (byte)2); qrt.reset(reset); patch=new PatchTableMessage((short)1, (short)3, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); qrt.patch(patch); patch=new PatchTableMessage((short)2, (short)4, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown - seq size changed btw patches"); } catch (BadPacketException e) { } qrt=new QueryRouteTable(); //c. missing sequence number 2 patch=new PatchTableMessage((short)1, (short)3, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); qrt.patch(patch); patch=new PatchTableMessage((short)3, (short)3, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown - missing sequence 2"); } catch (BadPacketException e) { } qrt=new QueryRouteTable(); //d. sequence interrupted by reset (is ok) patch=new PatchTableMessage((short)1, (short)3, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); qrt.patch(patch); reset=new ResetTableMessage(1024, (byte)2); qrt.reset(reset); patch=new PatchTableMessage((short)1, (short)6, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); qrt.patch(patch); qrt=new QueryRouteTable(); //e. More sequences than seq size sent patch=new PatchTableMessage((short)1, (short)2, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); qrt.patch(patch); patch=new PatchTableMessage((short)2, (short)2, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); qrt.patch(patch); patch=new PatchTableMessage((short)3, (short)2, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown - seq size: 2, but 3 are sent"); } catch (BadPacketException e) { } qrt=new QueryRouteTable(); //f. Unknown compress value patch=new PatchTableMessage((short)1, (short)2, (byte)0x2, (byte)8, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown -- unknown compressor"); } catch(BadPacketException e) { } qrt=new QueryRouteTable(); //g. Unable to uncompress patch=new PatchTableMessage((short)1, (short)2, PatchTableMessage.COMPRESSOR_DEFLATE, (byte)8, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown -- invalid compressed data"); } catch(BadPacketException e) { } qrt=new QueryRouteTable(); //h. Sending more data than table can hold reset=new ResetTableMessage(1024, (byte)2); qrt.reset(reset); patch=new PatchTableMessage((short)1, (short)6, PatchTableMessage.COMPRESSOR_NONE, (byte)8, new byte[1025], 0, 1025); try { qrt.patch(patch); fail("bpe should have been thrown - patched more than table size"); } catch(BadPacketException e) { } qrt=new QueryRouteTable(); //i. Unknown entryBits value. patch=new PatchTableMessage((short)1, (short)2, PatchTableMessage.COMPRESSOR_NONE, (byte)1, new byte[10], 0, 10); try { qrt.patch(patch); fail("bpe should have been thrown - invalid entry bits"); } catch(BadPacketException e) { } } }