From 1b54e64d34f87dca00a9d93bf110207a1098a2a5 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 9 Dec 2008 14:43:11 +0000 Subject: [PATCH] 2008-12-09 Tatsuhiro Tsujikawa Added bencode helper functions and BDE class. They will replace MetaFileUtil and Dictionary/List/Data classes. * src/Makefile.am * src/bencode.cc * src/bencode.h * test/BencodeTest.cc * test/Makefile.am --- ChangeLog | 10 + src/Makefile.am | 3 +- src/Makefile.in | 39 ++-- src/bencode.cc | 469 ++++++++++++++++++++++++++++++++++++++++++++ src/bencode.h | 214 ++++++++++++++++++++ test/BencodeTest.cc | 272 +++++++++++++++++++++++++ test/Makefile.am | 3 +- test/Makefile.in | 12 +- 8 files changed, 997 insertions(+), 25 deletions(-) create mode 100644 src/bencode.cc create mode 100644 src/bencode.h create mode 100644 test/BencodeTest.cc diff --git a/ChangeLog b/ChangeLog index 33526acd..639835a1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2008-12-09 Tatsuhiro Tsujikawa + + Added bencode helper functions and BDE class. + They will replace MetaFileUtil and Dictionary/List/Data classes. + * src/Makefile.am + * src/bencode.cc + * src/bencode.h + * test/BencodeTest.cc + * test/Makefile.am + 2008-12-09 Tatsuhiro Tsujikawa Fixed the bug that bad URI is sent to the tracker when the announe diff --git a/src/Makefile.am b/src/Makefile.am index 80b6f751..270da0fd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -193,7 +193,8 @@ SRCS = Socket.h\ NsCookieParser.cc NsCookieParser.h\ CookieStorage.cc CookieStorage.h\ SocketBuffer.cc SocketBuffer.h\ - OptionHandlerException.cc OptionHandlerException.h + OptionHandlerException.cc OptionHandlerException.h\ + bencode.cc bencode.h if ENABLE_SSL SRCS += TLSContext.h diff --git a/src/Makefile.in b/src/Makefile.in index 09f73dca..a8373ec6 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -417,9 +417,10 @@ am__libaria2c_a_SOURCES_DIST = Socket.h SocketCore.cc SocketCore.h \ ServerStatURISelector.h NsCookieParser.cc NsCookieParser.h \ CookieStorage.cc CookieStorage.h SocketBuffer.cc \ SocketBuffer.h OptionHandlerException.cc \ - OptionHandlerException.h TLSContext.h LibgnutlsTLSContext.cc \ - LibgnutlsTLSContext.h LibsslTLSContext.cc LibsslTLSContext.h \ - GZipDecoder.cc GZipDecoder.h Sqlite3MozCookieParser.cc \ + OptionHandlerException.h bencode.cc bencode.h TLSContext.h \ + LibgnutlsTLSContext.cc LibgnutlsTLSContext.h \ + LibsslTLSContext.cc LibsslTLSContext.h GZipDecoder.cc \ + GZipDecoder.h Sqlite3MozCookieParser.cc \ Sqlite3MozCookieParser.h AsyncNameResolver.cc \ AsyncNameResolver.h IteratableChunkChecksumValidator.cc \ IteratableChunkChecksumValidator.h \ @@ -813,14 +814,14 @@ am__objects_21 = SocketCore.$(OBJEXT) Command.$(OBJEXT) \ ServerStatMan.$(OBJEXT) InOrderURISelector.$(OBJEXT) \ ServerStatURISelector.$(OBJEXT) NsCookieParser.$(OBJEXT) \ CookieStorage.$(OBJEXT) SocketBuffer.$(OBJEXT) \ - OptionHandlerException.$(OBJEXT) $(am__objects_1) \ - $(am__objects_2) $(am__objects_3) $(am__objects_4) \ - $(am__objects_5) $(am__objects_6) $(am__objects_7) \ - $(am__objects_8) $(am__objects_9) $(am__objects_10) \ - $(am__objects_11) $(am__objects_12) $(am__objects_13) \ - $(am__objects_14) $(am__objects_15) $(am__objects_16) \ - $(am__objects_17) $(am__objects_18) $(am__objects_19) \ - $(am__objects_20) + OptionHandlerException.$(OBJEXT) bencode.$(OBJEXT) \ + $(am__objects_1) $(am__objects_2) $(am__objects_3) \ + $(am__objects_4) $(am__objects_5) $(am__objects_6) \ + $(am__objects_7) $(am__objects_8) $(am__objects_9) \ + $(am__objects_10) $(am__objects_11) $(am__objects_12) \ + $(am__objects_13) $(am__objects_14) $(am__objects_15) \ + $(am__objects_16) $(am__objects_17) $(am__objects_18) \ + $(am__objects_19) $(am__objects_20) am_libaria2c_a_OBJECTS = $(am__objects_21) libaria2c_a_OBJECTS = $(am_libaria2c_a_OBJECTS) am__installdirs = "$(DESTDIR)$(bindir)" @@ -1144,13 +1145,14 @@ SRCS = Socket.h SocketCore.cc SocketCore.h BinaryStream.h Command.cc \ ServerStatURISelector.h NsCookieParser.cc NsCookieParser.h \ CookieStorage.cc CookieStorage.h SocketBuffer.cc \ SocketBuffer.h OptionHandlerException.cc \ - OptionHandlerException.h $(am__append_1) $(am__append_2) \ - $(am__append_3) $(am__append_4) $(am__append_5) \ - $(am__append_6) $(am__append_7) $(am__append_8) \ - $(am__append_9) $(am__append_10) $(am__append_11) \ - $(am__append_12) $(am__append_13) $(am__append_14) \ - $(am__append_15) $(am__append_16) $(am__append_17) \ - $(am__append_18) $(am__append_19) $(am__append_20) + OptionHandlerException.h bencode.cc bencode.h $(am__append_1) \ + $(am__append_2) $(am__append_3) $(am__append_4) \ + $(am__append_5) $(am__append_6) $(am__append_7) \ + $(am__append_8) $(am__append_9) $(am__append_10) \ + $(am__append_11) $(am__append_12) $(am__append_13) \ + $(am__append_14) $(am__append_15) $(am__append_16) \ + $(am__append_17) $(am__append_18) $(am__append_19) \ + $(am__append_20) noinst_LIBRARIES = libaria2c.a libaria2c_a_SOURCES = $(SRCS) aria2c_LDADD = libaria2c.a @LIBINTL@ @ALLOCA@ @LIBGNUTLS_LIBS@\ @@ -1522,6 +1524,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/VersionMetalinkParserState.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XML2SAXMetalinkProcessor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asctime_r.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bencode.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/download_helper.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gai_strerror.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getaddrinfo.Po@am__quote@ diff --git a/src/bencode.cc b/src/bencode.cc new file mode 100644 index 00000000..60a790a2 --- /dev/null +++ b/src/bencode.cc @@ -0,0 +1,469 @@ +/* */ +#include "bencode.h" + +#include +#include + +#include "StringFormat.h" +#include "Util.h" + +namespace aria2 { + +namespace bencode { + +const BDE BDE::none; + +BDE::BDE() throw():_type(TYPE_NONE) {} + +BDE::BDE(Integer integer) throw():_type(TYPE_INTEGER), + _integer(new Integer(integer)) {} + + +BDE::BDE(const std::string& string) throw():_type(TYPE_STRING), + _string(new std::string(string)) {} + +BDE::BDE(const char* cstring) throw():_type(TYPE_STRING), + _string(new std::string(cstring)) {} + +BDE::BDE(const char* data, size_t length) throw(): + _type(TYPE_STRING), + _string(new std::string(&data[0], &data[length])) {} + +BDE::BDE(const unsigned char* data, size_t length) throw(): + _type(TYPE_STRING), + _string(new std::string(&data[0], &data[length])) {} + +BDE BDE::dict() throw() +{ + BDE bde; + bde._type = TYPE_DICT; + bde._dict.reset(new Dict()); + return bde; +} + +BDE BDE::list() throw() +{ + BDE bde; + bde._type = TYPE_LIST; + bde._list.reset(new List()); + return bde; +} + +// Test for Null data +bool BDE::isNone() const throw() +{ + return _type == TYPE_NONE; +} + +// Integer Interface + +bool BDE::isInteger() const throw() +{ + return _type == TYPE_INTEGER; +} + +BDE::Integer BDE::i() const throw(RecoverableException) +{ + if(isInteger()) { + return *_integer.get(); + } else { + throw RecoverableException("Not Integer"); + } +} + +// String Interface + +bool BDE::isString() const throw() +{ + return _type == TYPE_STRING; +} + +const std::string& BDE::s() const throw(RecoverableException) +{ + if(isString()) { + return *_string.get(); + } else { + throw RecoverableException("Not String"); + } +} + +// Dictionary Interface + +bool BDE::isDict() const throw() +{ + return _type == TYPE_DICT; +} + +BDE& BDE::operator[](const std::string& key) throw(RecoverableException) +{ + if(isDict()) { + return (*_dict.get())[key]; + } else { + throw RecoverableException("Not Dict"); + } +} + +const BDE& BDE::operator[](const std::string& key) const + throw(RecoverableException) +{ + if(isDict()) { + BDE::Dict::const_iterator i = _dict->find(key); + if(i == _dict->end()) { + return none; + } else { + return (*i).second; + } + } else { + throw RecoverableException("Not Dict"); + } +} + +bool BDE::containsKey(const std::string& key) const throw(RecoverableException) +{ + if(isDict()) { + return _dict->find(key) != _dict->end(); + } else { + throw RecoverableException("Not Dict"); + } +} + + +BDE::Dict::iterator BDE::dictBegin() throw(RecoverableException) +{ + if(isDict()) { + return _dict->begin(); + } else { + throw RecoverableException("Not Dict"); + } +} + +BDE::Dict::const_iterator BDE::dictBegin() const throw(RecoverableException) +{ + if(isDict()) { + return _dict->begin(); + } else { + throw RecoverableException("Not Dict"); + } +} + +BDE::Dict::iterator BDE::dictEnd() throw(RecoverableException) +{ + if(isDict()) { + return _dict->end(); + } else { + throw RecoverableException("Not Dict"); + } +} + +BDE::Dict::const_iterator BDE::dictEnd() const throw(RecoverableException) +{ + if(isDict()) { + return _dict->end(); + } else { + throw RecoverableException("Not Dict"); + } +} + +// List Interface + +bool BDE::isList() const throw() +{ + return _type == TYPE_LIST; +} + +void BDE::append(const BDE& bde) throw(RecoverableException) +{ + if(isList()) { + _list->push_back(bde); + } else { + throw RecoverableException("Not List"); + } +} + +void BDE::operator<<(const BDE& bde) throw(RecoverableException) +{ + if(isList()) { + _list->push_back(bde); + } else { + throw RecoverableException("Not List"); + } +} + +BDE& BDE::operator[](size_t index) throw(RecoverableException) +{ + if(isList()) { + return (*_list.get())[index]; + } else { + throw RecoverableException("Not List"); + } +} + +const BDE& BDE::operator[](size_t index) const throw(RecoverableException) +{ + if(isList()) { + return (*_list.get())[index]; + } else { + throw RecoverableException("Not List"); + } +} + +BDE::List::iterator BDE::listBegin() throw(RecoverableException) +{ + if(isList()) { + return _list->begin(); + } else { + throw RecoverableException("Not List"); + } +} + +BDE::List::const_iterator BDE::listBegin() const throw(RecoverableException) +{ + if(isList()) { + return _list->begin(); + } else { + throw RecoverableException("Not List"); + } +} + +BDE::List::iterator BDE::listEnd() throw(RecoverableException) +{ + if(isList()) { + return _list->end(); + } else { + throw RecoverableException("Not List"); + } +} + +BDE::List::const_iterator BDE::listEnd() const throw(RecoverableException) +{ + if(isList()) { + return _list->end(); + } else { + throw RecoverableException("Not List"); + } +} + +// Callable from List and Dict +size_t BDE::size() const throw(RecoverableException) +{ + if(isDict()) { + return _dict->size(); + } else if(isList()) { + return _list->size(); + } else { + throw RecoverableException("Not Dict nor List"); + } +} + +// Callable from List and Dict +bool BDE::empty() const throw(RecoverableException) +{ + if(isDict()) { + return _dict->empty(); + } else if(isList()) { + return _list->empty(); + } else { + throw RecoverableException("Not Dict nor List"); + } +} + +static BDE decodeiter(std::istream& ss) throw(RecoverableException); + +static void checkdelim(std::istream& ss, const char delim = ':') + throw(RecoverableException) +{ + char d; + if(!(ss.get(d) && d == delim)) { + throw RecoverableException + (StringFormat("Delimiter '%c' not found.", delim).str()); + } +} + +static std::string decoderawstring(std::istream& ss) + throw(RecoverableException) +{ + size_t length; + ss >> length; + if(!ss) { + throw RecoverableException("Integer expected but none found."); + } + // TODO check length, it must be less than or equal to INT_MAX + checkdelim(ss); + char* buf = new char[length]; + ss.read(buf, length); + if(ss.gcount() != static_cast(length)) { + throw RecoverableException + (StringFormat("Expected %lu bytes of data, but only %d read.", + static_cast(length), ss.gcount()).str()); + } + std::string str(&buf[0], &buf[length]); + delete [] buf; + return str; +} + +static BDE decodestring(std::istream& ss) throw(RecoverableException) +{ + return BDE(decoderawstring(ss)); +} + +static BDE decodeinteger(std::istream& ss) throw(RecoverableException) +{ + BDE::Integer integer; + ss >> integer; + if(!ss) { + throw RecoverableException("Integer expected but none found"); + } + checkdelim(ss, 'e'); + return BDE(integer); +} + +static BDE decodedict(std::istream& ss) throw(RecoverableException) +{ + BDE dict = BDE::dict(); + char c; + while(ss.get(c)) { + if(c == 'e') { + return dict; + } else { + ss.unget(); + std::string key = decoderawstring(ss); + dict[key] = decodeiter(ss); + } + } + throw RecoverableException("Unexpected EOF in dict context. 'e' expected."); +} + +static BDE decodelist(std::istream& ss) throw(RecoverableException) +{ + BDE list = BDE::list(); + char c; + while(ss.get(c)) { + if(c == 'e') { + return list; + } else { + ss.unget(); + list << decodeiter(ss); + } + } + throw RecoverableException("Unexpected EOF in list context. 'e' expected."); +} + +static BDE decodeiter(std::istream& ss) throw(RecoverableException) +{ + char c; + if(!ss.get(c)) { + throw RecoverableException("Unexpected EOF in term context." + " 'd', 'l', 'i' or digit is expected."); + } + if(c == 'd') { + return decodedict(ss); + } else if(c == 'l') { + return decodelist(ss); + } else if(c == 'i') { + return decodeinteger(ss); + } else { + ss.unget(); + return decodestring(ss); + } +} + +BDE decode(std::istream& in) throw(RecoverableException) +{ + return decodeiter(in); +} + +BDE decode(const std::string& s) throw(RecoverableException) +{ + if(s.empty()) { + return BDE::none; + } + std::istringstream ss(s); + + return decodeiter(ss); +} + +BDE decode(const unsigned char* data, size_t length) throw(RecoverableException) +{ + return decode(std::string(&data[0], &data[length])); +} + +BDE decodeFromFile(const std::string& filename) throw(RecoverableException) +{ + std::ifstream f(filename.c_str()); + if(f) { + return decode(f); + } else { + throw RecoverableException + (StringFormat("Cannot open file '%s'.", filename.c_str()).str()); + } +} + +static void encodeIter(std::ostream& o, const BDE& bde) throw() +{ + if(bde.isInteger()) { + o << "i" << bde.i() << "e"; + } else if(bde.isString()) { + const std::string& s = bde.s(); + o << s.size() << ":"; + o.write(s.data(), s.size()); + } else if(bde.isDict()) { + o << "d"; + for(BDE::Dict::const_iterator i = bde.dictBegin(); i != bde.dictEnd(); ++i){ + const std::string& key = (*i).first; + o << key.size() << ":"; + o.write(key.data(), key.size()); + encodeIter(o, (*i).second); + } + o << "e"; + } else if(bde.isList()) { + o << "l"; + for(BDE::List::const_iterator i = bde.listBegin(); i != bde.listEnd(); ++i){ + encodeIter(o, *i); + } + o << "e"; + } +} + +std::string encode(const BDE& bde) throw() +{ + std::ostringstream ss; + encodeIter(ss, bde); + return ss.str(); +} + +} // namespace bencode + +} // namespace aria2 diff --git a/src/bencode.h b/src/bencode.h new file mode 100644 index 00000000..2a7fcea3 --- /dev/null +++ b/src/bencode.h @@ -0,0 +1,214 @@ +/* */ +#include "common.h" + +#include +#include +#include +#include + +#include "SharedHandle.h" +#include "RecoverableException.h" + +namespace aria2 { + +namespace bencode { + +class BDE; + +class BDE { +public: + typedef std::map Dict; + typedef std::deque List; + typedef int64_t Integer; +private: + enum TYPE{ + TYPE_NONE, + TYPE_INTEGER, + TYPE_STRING, + TYPE_DICT, + TYPE_LIST, + }; + + TYPE _type; + SharedHandle _dict; + SharedHandle _list; + SharedHandle _string; + SharedHandle _integer; + +public: + BDE() throw(); + + static BDE dict() throw(); + + static BDE list() throw(); + + static const BDE none; + + // Test for Null data + // Return true if the type of this object is None. + bool isNone() const throw(); + + ////////////////////////////////////////////////////////////////////////////// + // Integer Interface + + BDE(Integer integer) throw(); + + // Returns true if the type of this object is Integer. + bool isInteger() const throw(); + + // Returns Integer. Requires this object to be Integer. + Integer i() const throw(RecoverableException); + + ////////////////////////////////////////////////////////////////////////////// + // String Interface + + BDE(const std::string& string) throw(); + + // Made explicit to avoid ambiguity with BDE(Integer). + explicit BDE(const char* cstring) throw(); + + BDE(const char* data, size_t length) throw(); + + BDE(const unsigned char* data, size_t length) throw(); + + // Returns true if the type of this object is String. + bool isString() const throw(); + + // Returns std::string. Requires this object to be String + const std::string& s() const throw(RecoverableException); + + ////////////////////////////////////////////////////////////////////////////// + // Dictionary Interface + + // Returns true if the type of this object is Dict. + bool isDict() const throw(); + + // Returns the reference to BDE object associated with given key. + // If the key is not found, new pair with that key is created using default + // values, which is then returned. In other words, this is the same behavior + // of std::map's operator[]. + // Requires this object to be Dict. + BDE& operator[](const std::string& key) throw(RecoverableException); + + // Returns the const reference to BDE ojbect associated with given key. + // If the key is not found, BDE::none is returned. + // Requires this object to be Dict. + const BDE& operator[](const std::string& key) const + throw(RecoverableException); + + // Returns true if the given key is found in dict. + // Requires this object to be Dict. + bool containsKey(const std::string& key) const throw(RecoverableException); + + // Returns a read/write iterator that points to the first pair in the dict. + // Requires this object to be Dict. + Dict::iterator dictBegin() throw(RecoverableException); + + // Returns a read/write read-only iterator that points to the first pair in + // the dict. + // Requires this object to be Dict. + Dict::const_iterator dictBegin() const throw(RecoverableException); + + // Returns a read/write read-only iterator that points to one past the last + // pair in the dict. + // Requires this object to be Dict. + Dict::iterator dictEnd() throw(RecoverableException); + + // Returns a read/write read-only iterator that points to one past the last + // pair in the dict. + // Requires this object to be Dict. + Dict::const_iterator dictEnd() const throw(RecoverableException); + + ////////////////////////////////////////////////////////////////////////////// + // List Interface + + // Returns true if the type of this object is List. + bool isList() const throw(); + + // Appends given bde to list. Required the type of this object to be List. + void append(const BDE& bde) throw(RecoverableException); + + // Alias for append() + void operator<<(const BDE& bde) throw(RecoverableException); + + // Returns the reference of the object at the given index. Required this + // object to be List. + BDE& operator[](size_t index) throw(RecoverableException); + + // Returns the const reference of the object at the given index. + // Required this object to be List. + const BDE& operator[](size_t index) const throw(RecoverableException); + + // Returns a read/write iterator that points to the first object in list. + // Required this object to be List. + List::iterator listBegin() throw(RecoverableException); + + // Returns a read/write read-only iterator that points to the first object + // in list. Required this object to be List. + List::const_iterator listBegin() const throw(RecoverableException); + + // Returns a read/write iterator that points to the one past the last object + // in list. Required this object to be List. + List::iterator listEnd() throw(RecoverableException); + + // Returns a read/write read-only iterator that points to the one past the + // last object in list. Required this object to be List. + List::const_iterator listEnd() const throw(RecoverableException); + + // For List type: Returns size of list. + // For Dict type: Returns size of dict. + size_t size() const throw(RecoverableException); + + // For List type: Returns true if size of list is 0. + // For Dict type: Returns true if size of dict is 0. + bool empty() const throw(RecoverableException); +}; + +BDE decode(std::istream& in) throw(RecoverableException); + +// Decode the data in s. +BDE decode(const std::string& s) throw(RecoverableException); + +BDE decode(const unsigned char* data, size_t length) + throw(RecoverableException); + +BDE decodeFromFile(const std::string& filename) throw(RecoverableException); + +std::string encode(const BDE& bde) throw(); + +} // namespace bencode + +} // namespace aria2 diff --git a/test/BencodeTest.cc b/test/BencodeTest.cc new file mode 100644 index 00000000..8db1b1f5 --- /dev/null +++ b/test/BencodeTest.cc @@ -0,0 +1,272 @@ +#include "bencode.h" + +#include +#include + +#include + +namespace aria2 { + +class BencodeTest:public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(BencodeTest); + CPPUNIT_TEST(testString); + CPPUNIT_TEST(testInteger); + CPPUNIT_TEST(testDict); + CPPUNIT_TEST(testDictIter); + CPPUNIT_TEST(testList); + CPPUNIT_TEST(testListIter); + CPPUNIT_TEST(testDecode); + CPPUNIT_TEST(testEncode); + CPPUNIT_TEST_SUITE_END(); +private: + +public: + void testString(); + void testInteger(); + void testDict(); + void testDictIter(); + void testList(); + void testListIter(); + void testDecode(); + void testEncode(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION( BencodeTest ); + +void BencodeTest::testString() +{ + bencode::BDE s(std::string("aria2")); + CPPUNIT_ASSERT_EQUAL(std::string("aria2"), s.s()); + + unsigned char dataWithNull[] = { 0xf0, '\0', 0x0f }; + bencode::BDE sWithNull(dataWithNull, sizeof(dataWithNull)); + CPPUNIT_ASSERT(memcmp(dataWithNull, sWithNull.s().c_str(), + sizeof(dataWithNull)) == 0); + + bencode::BDE zero(""); + CPPUNIT_ASSERT_EQUAL(std::string(""), zero.s()); +} + +void BencodeTest::testInteger() +{ + bencode::BDE integer(INT64_MAX); + CPPUNIT_ASSERT_EQUAL(INT64_MAX, integer.i()); +} + +void BencodeTest::testDict() +{ + bencode::BDE dict = bencode::BDE::dict(); + CPPUNIT_ASSERT(dict.empty()); + + dict["ki"] = 7; + dict["ks"] = std::string("abc"); + + CPPUNIT_ASSERT_EQUAL(static_cast(2), dict.size()); + CPPUNIT_ASSERT(dict.containsKey("ki")); + CPPUNIT_ASSERT_EQUAL(static_cast(7), dict["ki"].i()); + CPPUNIT_ASSERT(dict.containsKey("ks")); + CPPUNIT_ASSERT_EQUAL(std::string("abc"), dict["ks"].s()); + + CPPUNIT_ASSERT(dict["kn"].isNone()); // This adds kn key with default value. + CPPUNIT_ASSERT_EQUAL(static_cast(3), dict.size()); + CPPUNIT_ASSERT(dict.containsKey("kn")); + + const bencode::BDE& ref = dict; + ref["kn2"]; // This doesn't add kn2 key. + CPPUNIT_ASSERT_EQUAL(static_cast(3), ref.size()); + CPPUNIT_ASSERT(!ref.containsKey("kn2")); +} + +void BencodeTest::testDictIter() +{ + bencode::BDE dict = bencode::BDE::dict(); + dict["alpha2"] = std::string("alpha2"); + dict["charlie"] = std::string("charlie"); + dict["bravo"] = std::string("bravo"); + dict["alpha"] = std::string("alpha"); + + bencode::BDE::Dict::iterator i = dict.dictBegin(); + CPPUNIT_ASSERT_EQUAL(std::string("alpha"), (*i++).first); + CPPUNIT_ASSERT_EQUAL(std::string("alpha2"), (*i++).first); + CPPUNIT_ASSERT_EQUAL(std::string("bravo"), (*i++).first); + CPPUNIT_ASSERT_EQUAL(std::string("charlie"), (*i++).first); + CPPUNIT_ASSERT(dict.dictEnd() == i); + + const bencode::BDE& ref = dict; + bencode::BDE::Dict::const_iterator ci = ref.dictBegin(); + CPPUNIT_ASSERT_EQUAL(std::string("alpha"), (*ci++).first); + std::advance(ci, 3); + CPPUNIT_ASSERT(ref.dictEnd() == ci); +} + +void BencodeTest::testList() +{ + bencode::BDE list = bencode::BDE::list(); + CPPUNIT_ASSERT(list.empty()); + list << 7; + list << std::string("aria2"); + + CPPUNIT_ASSERT_EQUAL(static_cast(2), list.size()); + CPPUNIT_ASSERT_EQUAL(static_cast(7), list[0].i()); + CPPUNIT_ASSERT_EQUAL(std::string("aria2"), list[1].s()); + + const bencode::BDE& ref = list; + CPPUNIT_ASSERT_EQUAL(static_cast(7), ref[0].i()); + CPPUNIT_ASSERT_EQUAL(std::string("aria2"), ref[1].s()); +} + +void BencodeTest::testListIter() +{ + bencode::BDE list = bencode::BDE::list(); + list << std::string("alpha2"); + list << std::string("charlie"); + list << std::string("bravo"); + list << std::string("alpha"); + + bencode::BDE::List::iterator i = list.listBegin(); + CPPUNIT_ASSERT_EQUAL(std::string("alpha2"), (*i++).s()); + CPPUNIT_ASSERT_EQUAL(std::string("charlie"), (*i++).s()); + CPPUNIT_ASSERT_EQUAL(std::string("bravo"), (*i++).s()); + CPPUNIT_ASSERT_EQUAL(std::string("alpha"), (*i++).s()); + CPPUNIT_ASSERT(list.listEnd() == i); + + const bencode::BDE& ref = list; + bencode::BDE::List::const_iterator ci = ref.listBegin(); + CPPUNIT_ASSERT_EQUAL(std::string("alpha2"), (*ci++).s()); + std::advance(ci, 3); + CPPUNIT_ASSERT(ref.listEnd() == ci); +} + +void BencodeTest::testDecode() +{ + { + // string, integer and list in dict + bencode::BDE dict = + bencode::decode("d4:name5:aria24:sizei12345678900e5:filesl3:bin3:docee"); + CPPUNIT_ASSERT(dict.isDict()); + CPPUNIT_ASSERT_EQUAL(std::string("aria2"), dict["name"].s()); + CPPUNIT_ASSERT_EQUAL(static_cast(12345678900LL), + dict["size"].i()); + bencode::BDE list = dict["files"]; + CPPUNIT_ASSERT(list.isList()); + CPPUNIT_ASSERT_EQUAL(static_cast(2), list.size()); + CPPUNIT_ASSERT_EQUAL(std::string("bin"), list[0].s()); + CPPUNIT_ASSERT_EQUAL(std::string("doc"), list[1].s()); + } + { + // dict in list + bencode::BDE list = bencode::decode("ld1:ki123eee"); + CPPUNIT_ASSERT(list.isList()); + CPPUNIT_ASSERT_EQUAL(static_cast(1), list.size()); + bencode::BDE dict = list[0]; + CPPUNIT_ASSERT(dict.isDict()); + CPPUNIT_ASSERT_EQUAL(static_cast(123), + dict["k"].i()); + } + { + // empty key is allowed + bencode::BDE s = bencode::decode("d0:1:ve"); + } + { + // empty string + bencode::BDE s = bencode::decode("0:"); + CPPUNIT_ASSERT_EQUAL(std::string(""), s.s()); + } + { + // empty dict + bencode::BDE d = bencode::decode("de"); + CPPUNIT_ASSERT(d.empty()); + } + { + // empty list + bencode::BDE l = bencode::decode("le"); + CPPUNIT_ASSERT(l.empty()); + } + { + // integer, without ending 'e' + try { + bencode::decode("i3"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + CPPUNIT_ASSERT_EQUAL(std::string("Delimiter 'e' not found."), + std::string(e.what())); + } + } + { + // dict, without ending 'e' + try { + bencode::decode("d"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + CPPUNIT_ASSERT_EQUAL(std::string("Unexpected EOF in dict context." + " 'e' expected."), + std::string(e.what())); + } + } + { + // list, without ending 'e' + try { + bencode::decode("l"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + CPPUNIT_ASSERT_EQUAL(std::string("Unexpected EOF in list context." + " 'e' expected."), + std::string(e.what())); + } + } + { + // string, less than the specified length. + try { + bencode::decode("3:ab"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + CPPUNIT_ASSERT_EQUAL(std::string("Expected 3 bytes of data," + " but only 2 read."), + std::string(e.what())); + } + } + { + // string, but length is invalid + try { + bencode::decode("x:abc"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + CPPUNIT_ASSERT_EQUAL(std::string("Integer expected but none found."), + std::string(e.what())); + } + } + { + // empty encoded data + CPPUNIT_ASSERT(bencode::decode("").isNone()); + } + { + // ignore trailing garbage at the end of the input. + bencode::BDE s = bencode::decode("5:aria2trail"); + CPPUNIT_ASSERT_EQUAL(std::string("aria2"), s.s()); + } +} + +void BencodeTest::testEncode() +{ + { + bencode::BDE dict = bencode::BDE::dict(); + dict["name"] = std::string("aria2"); + dict["loc"] = 80000; + dict["files"] = bencode::BDE::list(); + dict["files"] << std::string("aria2c"); + dict["attrs"] = bencode::BDE::dict(); + dict["attrs"]["license"] = std::string("GPL"); + + CPPUNIT_ASSERT_EQUAL(std::string("d" + "5:attrsd7:license3:GPLe" + "5:filesl6:aria2ce" + "3:loci80000e" + "4:name5:aria2" + "e"), + bencode::encode(dict)); + } +} + +} // namespace aria2 diff --git a/test/Makefile.am b/test/Makefile.am index ece04638..35baefb7 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -63,7 +63,8 @@ aria2c_SOURCES = AllTest.cc\ FtpConnectionTest.cc\ OptionParserTest.cc\ SimpleDNSCacheTest.cc\ - DownloadHelperTest.cc + DownloadHelperTest.cc\ + BencodeTest.cc if HAVE_LIBZ aria2c_SOURCES += GZipDecoderTest.cc diff --git a/test/Makefile.in b/test/Makefile.in index 63806457..a4ad2f90 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -197,7 +197,7 @@ am__aria2c_SOURCES_DIST = AllTest.cc TestUtil.cc TestUtil.h \ DirectDiskAdaptorTest.cc CookieTest.cc CookieStorageTest.cc \ TimeTest.cc CopyDiskAdaptorTest.cc FtpConnectionTest.cc \ OptionParserTest.cc SimpleDNSCacheTest.cc \ - DownloadHelperTest.cc GZipDecoderTest.cc \ + DownloadHelperTest.cc BencodeTest.cc GZipDecoderTest.cc \ Sqlite3MozCookieParserTest.cc MessageDigestHelperTest.cc \ IteratableChunkChecksumValidatorTest.cc \ IteratableChecksumValidatorTest.cc BtAllowedFastMessageTest.cc \ @@ -371,8 +371,8 @@ am_aria2c_OBJECTS = AllTest.$(OBJEXT) TestUtil.$(OBJEXT) \ TimeTest.$(OBJEXT) CopyDiskAdaptorTest.$(OBJEXT) \ FtpConnectionTest.$(OBJEXT) OptionParserTest.$(OBJEXT) \ SimpleDNSCacheTest.$(OBJEXT) DownloadHelperTest.$(OBJEXT) \ - $(am__objects_1) $(am__objects_2) $(am__objects_3) \ - $(am__objects_4) $(am__objects_5) + BencodeTest.$(OBJEXT) $(am__objects_1) $(am__objects_2) \ + $(am__objects_3) $(am__objects_4) $(am__objects_5) aria2c_OBJECTS = $(am_aria2c_OBJECTS) am__DEPENDENCIES_1 = aria2c_DEPENDENCIES = ../src/libaria2c.a ../src/download_helper.o \ @@ -595,8 +595,9 @@ aria2c_SOURCES = AllTest.cc TestUtil.cc TestUtil.h SocketCoreTest.cc \ DirectDiskAdaptorTest.cc CookieTest.cc CookieStorageTest.cc \ TimeTest.cc CopyDiskAdaptorTest.cc FtpConnectionTest.cc \ OptionParserTest.cc SimpleDNSCacheTest.cc \ - DownloadHelperTest.cc $(am__append_1) $(am__append_2) \ - $(am__append_3) $(am__append_4) $(am__append_5) + DownloadHelperTest.cc BencodeTest.cc $(am__append_1) \ + $(am__append_2) $(am__append_3) $(am__append_4) \ + $(am__append_5) #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64 #aria2c_LDFLAGS = ${CPPUNIT_LIBS} @@ -692,6 +693,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AuthConfigFactoryTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BNodeTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Base64Test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BencodeTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BencodeVisitorTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BitfieldManTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BtAllowedFastMessageTest.Po@am__quote@