package com.limegroup.bittorrent.bencoding; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.Test; import com.limegroup.gnutella.util.BaseTestCase; public class BEncodeTest extends BaseTestCase { public BEncodeTest(String name) { super(name); } public static Test suite() { return buildTestSuite(BEncodeTest.class); } private static class TestReadChannel implements ReadableByteChannel { public ByteBuffer src; public boolean closed; public void setString(String src) { this.src = ByteBuffer.wrap(src.getBytes()); closed = false; } public int read(ByteBuffer dst) throws IOException { if (!src.hasRemaining()) return closed ? -1 : 0; int position = src.position(); src.limit(Math.min(src.capacity(),src.position()+dst.remaining())); dst.put(src); src.limit(src.capacity()); return src.position() - position; } public void close() throws IOException { closed = true; } public boolean isOpen() { return !closed; } } static TestReadChannel chan = new TestReadChannel(); public void testTokenRecognition() throws Exception { // nothing chan.setString(""); Token t = Token.getNextToken(chan); assertNull(t); // long chan.setString("iSomething"); t = Token.getNextToken(chan); assertEquals(Token.LONG,t.getType()); assertEquals(1,chan.src.position()); // string chan.setString("1adf"); t = Token.getNextToken(chan); assertEquals(Token.STRING,t.getType()); assertEquals(1,chan.src.position()); // list chan.setString("ladf"); t = Token.getNextToken(chan); assertEquals(Token.LIST,t.getType()); assertEquals(1,chan.src.position()); // dictionary chan.setString("dadf"); t = Token.getNextToken(chan); assertEquals(Token.DICTIONARY,t.getType()); assertEquals(1,chan.src.position()); } /** * Tests some scenarios of creating a string. */ public void testString() throws Exception { // test a regular string chan.setString("4:asdf"); Token t = Token.getNextToken(chan); t.handleRead(); assertNotNull(t.getResult()); assertEquals(Token.STRING, t.getType()); assertEquals("asdf", t.getResult()); // make sure the channel has been consumed completely assertFalse(chan.src.hasRemaining()); // repeat with some extra data to the channel chan.setString("4:asdfasdf"); t = Token.getNextToken(chan); t.handleRead(); assertNotNull(t.getResult()); assertEquals(Token.STRING, t.getType()); assertEquals("asdf", t.getResult()); // there should be 4 bytes left in the channel assertEquals(4,chan.src.remaining()); // try sending the values byte by byte chan.setString("2"); t = Token.getNextToken(chan); t.handleRead(); assertNull(t.getResult()); chan.setString(":"); t.handleRead(); assertNull(t.getResult()); chan.setString("a"); t.handleRead(); assertNull(t.getResult()); chan.setString("s"); t.handleRead(); assertNotNull(t.getResult()); assertEquals("as",t.getResult()); // try an empty read call chan.setString("2:a"); t = Token.getNextToken(chan); t.handleRead(); assertNull(t.getResult()); t.handleRead(); chan.setString("s"); t.handleRead(); assertNotNull(t.getResult()); assertEquals("as",t.getResult()); // try a closed channel before end of string chan.setString("2:a"); chan.close(); t = Token.getNextToken(chan); try { t.handleRead(); fail("trying to read from closed channel should throw"); } catch (IOException expected){} // test an invalid string chan.setString("2;as"); t = Token.getNextToken(chan); assertEquals(Token.STRING,t.getType()); try { t.handleRead(); fail("invalid string should throw"); } catch (IOException expected){} assertEquals(2,chan.src.position()); // should stop reading after the invalid char. // test an empty string chan.setString("0:"); t = Token.getNextToken(chan); assertEquals(Token.STRING,t.getType()); t.handleRead(); assertEquals("",t.getResult()); } /** * tests various scenarios of parsing a Long value. */ public void testLong() throws Exception { // test some empty calls chan.setString("i"); Token t = Token.getNextToken(chan); assertEquals(Token.LONG, t.getType()); t.handleRead(); assertNull(t.getResult()); t.handleRead(); assertNull(t.getResult()); // test adding the values digit by digit chan.setString("1"); t.handleRead(); assertNull(t.getResult()); chan.setString("2"); t.handleRead(); assertNull(t.getResult()); chan.setString("3"); t.handleRead(); assertNull(t.getResult()); chan.setString("e"); t.handleRead(); assertNotNull(t.getResult()); assertEquals(new Long(123),t.getResult()); // test having extra data in channel chan.setString("i123easdfadfs"); t = Token.getNextToken(chan); t.handleRead(); assertEquals(new Long(123), t.getResult()); assertEquals(5, chan.src.position()); assertTrue(chan.src.hasRemaining()); // 0 chan.setString("i0e"); t = Token.getNextToken(chan); t.handleRead(); assertEquals(new Long(0),t.getResult()); // negative values chan.setString("i-1e"); t = Token.getNextToken(chan); t.handleRead(); assertEquals(new Long(-1),t.getResult()); // leading 0s are invalid chan.setString("i001e"); t = Token.getNextToken(chan); try { t.handleRead(); fail(" leading 0s didn't throw"); } catch (IOException expected) {} assertNull(t.getResult()); assertEquals(3, chan.src.position()); // negative 0 is invalid chan.setString("i-01e"); t = Token.getNextToken(chan); try { t.handleRead(); fail(" negative 0 didn't throw"); } catch (IOException expected) {} assertNull(t.getResult()); assertEquals(3, chan.src.position()); // any non-numeric char is invalid chan.setString("i13i1e"); t = Token.getNextToken(chan); try { t.handleRead(); fail(" invalid chars didn't throw"); } catch (IOException expected) {} assertNull(t.getResult()); assertEquals(4, chan.src.position()); } /** * test various scenarios of parsing a List */ public void testList() throws Exception { // few empty calls chan.setString("l"); Token t = Token.getNextToken(chan); assertEquals(Token.LIST, t.getType()); t.handleRead(); assertNull(t.getResult()); t.handleRead(); assertNull(t.getResult()); // create a list char at a time chan.setString("i5e"); t.handleRead(); assertNull(t.getResult()); chan.setString("e"); t.handleRead(); assertNotNull(t.getResult()); List l = (List)t.getResult(); assertEquals(1,l.size()); assertTrue(l.contains(new Long(5))); // an empty list chan.setString("le"); t = Token.getNextToken(chan); t.handleRead(); l = (List) t.getResult(); assertTrue(l.isEmpty()); // test that no extra data is read chan.setString("l2:asi44eei56e"); t = Token.getNextToken(chan); t.handleRead(); l = (List) t.getResult(); assertEquals(2, l.size()); assertTrue(l.contains("as")); assertTrue(l.contains(new Long(44))); assertEquals(4, chan.src.remaining()); } /** * Tests various scenarios of parsing a dictionary */ public void testDictionary() throws Exception { // few empty calls chan.setString("d"); Token t = Token.getNextToken(chan); assertEquals(Token.DICTIONARY, t.getType()); t.handleRead(); assertNull(t.getResult()); t.handleRead(); assertNull(t.getResult()); // create a dictionary one char at a time chan.setString("2:as2:df"); t.handleRead(); assertNull(t.getResult()); chan.setString("e"); t.handleRead(); assertNotNull(t.getResult()); Map m = (Map)t.getResult(); assertEquals(1,m.size()); assertTrue(m.containsKey("as")); assertTrue(m.containsValue("df")); // an empty dictionary chan.setString("de"); t = Token.getNextToken(chan); t.handleRead(); m = (Map)t.getResult(); assertTrue(m.isEmpty()); // no extra characters read chan.setString("d1:ai1eei45e"); t = Token.getNextToken(chan); t.handleRead(); m = (Map)t.getResult(); assertEquals(1,m.size()); assertTrue(chan.src.hasRemaining()); assertEquals(4, chan.src.remaining()); // invalid key chan.setString("di4ei5ee"); t = Token.getNextToken(chan); try { t.handleRead(); fail("non-string key didn't throw"); } catch (IOException expected){} // missing value chan.setString("d1:ae"); t = Token.getNextToken(chan); try { t.handleRead(); fail("missing value didn't throw"); } catch (IOException expected){} } /** * tests encoding and decoding a fancy nested collection structure */ public void testNested() throws Exception { // { key1 -> { key11 -> [badger, badger, 3, [mushroom, mushroom], {}], // key12 -> []} // key2 -> [[[[snake],snake]]]} List l = new ArrayList(); l.add("snake"); List l2 = new ArrayList(); l2.add(l); l2.add("snake"); l = new ArrayList(); l.add(l2); l2 = new ArrayList(); l2.add(l); Map m = new HashMap(); m.put("key2",l2); Map m2 = new HashMap(); m.put("key1",m2); m2.put("key12",new ArrayList()); l = new ArrayList(); l.add("badger"); l.add("badger"); l.add(new Long(3)); l2 = new ArrayList(); l.add(l2); l.add(new HashMap()); l2.add("mushroom"); l2.add("mushroom"); m2.put("key11",l); // this finishes the fancy object ByteArrayOutputStream baos = new ByteArrayOutputStream(); BEncoder.encodeDict(baos,m); String s = new String(baos.toByteArray(),Token.ASCII); String expected = "d4:key1d5:key11l6:badger6:badgeri3el8:mushroom8:mushroomedee5:key12lee4:key2llll5:snakee5:snakeeeee"; assertEquals(expected, s); chan.setString(s); Token t = Token.getNextToken(chan); t.handleRead(); Map outtest = (Map)t.getResult(); assertEquals(2, outtest.size()); Map inner = (Map)outtest.get("key1"); List empty = (List) inner.get("key12"); assertTrue(empty.isEmpty()); List badgers = (List) inner.get("key11"); assertEquals(5, badgers.size()); assertEquals("badger",badgers.get(0)); assertEquals("badger",badgers.get(1)); assertEquals(new Long(3),badgers.get(2)); List mushrooms = (List)badgers.get(3); assertEquals(2,mushrooms.size()); assertEquals("mushroom",mushrooms.get(0)); assertEquals("mushroom",mushrooms.get(1)); Map emptyMap = (Map)badgers.get(4); assertTrue(emptyMap.isEmpty()); List nestedList0 = (List)outtest.get("key2"); assertEquals(1,nestedList0.size()); List nestedList1 = (List)nestedList0.get(0); assertEquals(1,nestedList1.size()); List nestedList2 = (List)nestedList1.get(0); assertEquals(2,nestedList2.size()); assertTrue(nestedList2.contains("snake")); List nestedList3 = (List) nestedList2.get(0); assertEquals(1,nestedList3.size()); assertTrue(nestedList3.contains("snake")); } }