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"));
}
}