#include #include #include #include #include #include #include using TestBuffer = std::vector; template struct TestReadData { TestBuffer buffer; bool rc; Result result; }; using TestReadInt = TestReadData; using TestReadString = TestReadData; template std::ostream& operator<<(std::ostream& os, const TestReadData& d) { os << "buf = [ "; for (auto x : d.buffer) { os << x << " "; } os << "] rc = "; os << std::boolalpha << d.rc << " result = " << d.result; return os; } static constexpr byte_t i = 'i'; static constexpr byte_t e = 'e'; static constexpr byte_t zero = '0'; static constexpr byte_t one = '1'; static constexpr byte_t two = '2'; static constexpr byte_t f = 'f'; static constexpr byte_t z = 'z'; static constexpr byte_t colon = ':'; std::vector testReadInt{ // good cases {{i, 0, e}, true, 0}, {{i, zero, e}, true, 0}, {{i, one, e}, true, 1}, {{i, two, e}, true, 2}, {{i, two, e, e, e}, true, 2}, {{i, one, one, one, two, e}, true, 1112}, {{i, f, e}, true, 0}, {{i, z, e}, true, 0}, {{i, one, two, e, one, one}, true, 12}, // failure cases {{i, e}, false, 0}, {{e}, false, 0}, {{z}, false, 0}, }; TEST_CASE("Read int", "[bencode]") { auto d = GENERATE(from_range(testReadInt)); llarp_buffer_t buffer(d.buffer); uint64_t result = 0; bool rc = bencode_read_integer(&buffer, &result); CHECK(rc == d.rc); CHECK(result == d.result); } std::vector testReadStr{ // good cases {{one, colon, 'a'}, true, "a"}, {{one, colon, 'b'}, true, "b"}, {{two, colon, f, z}, true, "fz"}, {{two, colon, f, z, f, f}, true, "fz"}, {{zero, colon}, true, ""}, // failure cases {{two, colon, f}, false, ""}, {{two, f}, false, ""}, {{'-', one, colon, f}, false, ""}, {{f}, false, ""}, {{one, f, colon}, false, ""}, {{colon}, false, ""}, {{colon, colon}, false, ""}, }; TEST_CASE("Read str", "[bencode]") { auto d = GENERATE(from_range(testReadStr)); llarp_buffer_t buffer(d.buffer); llarp_buffer_t result; bool rc = bencode_read_string(&buffer, &result); CHECK(rc == d.rc); CHECK(result.sz == d.result.size()); CHECK(std::string(result.base, result.base + result.sz) == d.result); } template struct TestWriteData { Input input; size_t bufferSize; bool rc; std::string output; }; using TestWriteByteString = TestWriteData; using TestWriteInt = TestWriteData; static constexpr size_t MAX_1 = static_cast(std::numeric_limits::max()) + 1; std::vector testWriteByteString{ // good cases {"abacus", 100, true, "6:abacus"}, {" abacus", 100, true, "8: abacus"}, {"", 100, true, "0:"}, {std::string("\0\0\0", 3), 100, true, std::string("3:\0\0\0", 5)}, {std::string(MAX_1, 'a'), MAX_1 + 100, true, std::to_string(MAX_1) + std::string(":") + std::string(MAX_1, 'a')}, // bad cases {"a", 1, false, ""}, }; TEST_CASE("Write byte str", "[bencode]") { auto d = GENERATE(from_range(testWriteByteString)); std::vector backingBuffer(d.bufferSize, 0); llarp_buffer_t buffer(backingBuffer); bool rc = bencode_write_bytestring(&buffer, d.input.data(), d.input.size()); REQUIRE(rc == d.rc); REQUIRE(std::string(buffer.base, buffer.cur) == d.output); } std::vector testWriteInt{ // Good cases {0, 100, true, "i0e"}, {1234, 100, true, "i1234e"}, {uint64_t(-1), 100, true, "i18446744073709551615e"}, // Bad cases {1234567, 3, false, ""}, }; TEST_CASE("Write int", "[bencode]") { auto d = GENERATE(from_range(testWriteInt)); std::vector backingBuffer(d.bufferSize, 0); llarp_buffer_t buffer(backingBuffer); bool rc = bencode_write_uint64(&buffer, d.input); REQUIRE(rc == d.rc); REQUIRE(std::string(buffer.base, buffer.cur) == d.output); } TEST_CASE("Write int values", "[bencode]") { // test we can encode any uint64_t into a buffer. uint64_t val = GENERATE( std::numeric_limits::min(), std::numeric_limits::max(), std::numeric_limits::max() / 2, std::numeric_limits::max() / 3); std::vector backingBuffer(100, 0); { llarp_buffer_t buffer(backingBuffer); bool rc = bencode_write_uint64(&buffer, val); REQUIRE(rc); } { uint64_t result = 0; llarp_buffer_t buffer(backingBuffer); bool rc = bencode_read_integer(&buffer, &result); REQUIRE(rc); REQUIRE(result == val); } } TEST_CASE("Bencode: good uint64 entry", "[bencode]") { std::vector backingBuffer(100, 0); llarp_buffer_t buffer(backingBuffer); REQUIRE(bencode_write_uint64_entry(&buffer, "v", 1, 0)); REQUIRE(std::string(buffer.base, buffer.cur) == "1:vi0e"); } TEST_CASE("Bencode: bad uint64 entry", "[bencode]") { std::vector otherBuffer(1, 0); llarp_buffer_t buffer(otherBuffer); REQUIRE_FALSE(bencode_write_uint64_entry(&buffer, "v", 1, 0)); } struct ValueData { // Variant-ish std::string theString; uint64_t theInt; bool isString; }; struct ListTestData { std::vector list; size_t bufferSize; std::string result; }; std::vector listTestData{ {{}, 100, "le"}, {{{"", 0, true}}, 100, "l0:e"}, {{{"", 0, false}}, 100, "li0ee"}, {{{"", 0, false}, {"", 0, true}}, 100, "li0e0:e"}, {{{"", 123, false}, {"abc", 0, true}}, 100, "li123e3:abce"}, {{{"", 123, false}, {"abc", 0, true}, {"abc", 0, true}}, 100, "li123e3:abc3:abce"}, }; TEST_CASE("List test", "[bencode]") { auto d = GENERATE(from_range(listTestData)); std::vector backingBuffer(d.bufferSize, 0); llarp_buffer_t buffer(backingBuffer); REQUIRE(bencode_start_list(&buffer)); for (const auto& x : d.list) { if (x.isString) { REQUIRE(bencode_write_bytestring(&buffer, x.theString.data(), x.theString.size())); } else { REQUIRE(bencode_write_uint64(&buffer, x.theInt)); } } REQUIRE(bencode_end(&buffer)); REQUIRE(std::string(buffer.base, buffer.cur) == d.result); } struct DictTestData { std::vector> list; size_t bufferSize; std::string result; }; std::vector dictTestData{ {{}, 100, "de"}, {{{'a', {"", 0, true}}}, 100, "d1:a0:e"}, {{{'b', {"", 0, false}}}, 100, "d1:bi0ee"}, {{{'c', {"", 0, false}}, {'d', {"", 0, true}}}, 100, "d1:ci0e1:d0:e"}, {{{'e', {"", 123, false}}, {'f', {"abc", 0, true}}}, 100, "d1:ei123e1:f3:abce"}, {{{'a', {"", 123, false}}, {'b', {"abc", 0, true}}, {'c', {"abc", 0, true}}}, 100, "d1:ai123e1:b3:abc1:c3:abce"}, }; TEST_CASE("Dict test", "[bencode]") { auto d = GENERATE(from_range(dictTestData)); std::vector backingBuffer(d.bufferSize, 0); llarp_buffer_t buffer(backingBuffer); REQUIRE(bencode_start_dict(&buffer)); for (const auto& x : d.list) { REQUIRE(bencode_write_bytestring(&buffer, &x.first, 1)); if (x.second.isString) { REQUIRE( bencode_write_bytestring(&buffer, x.second.theString.data(), x.second.theString.size())); } else { REQUIRE(bencode_write_uint64(&buffer, x.second.theInt)); } } REQUIRE(bencode_end(&buffer)); REQUIRE(std::string(buffer.base, buffer.cur) == d.result); } struct ReadData { std::string input; std::vector output; }; std::vector dictReadData{ {"de", {}}, {"d1:a0:e", {"a", ""}}, {"d1:be", {"b"}}, {"d1:b2:23e", {"b", "23"}}}; TEST_CASE("Read dict", "[bencode]") { auto d = GENERATE(from_range(dictReadData)); byte_t* input = const_cast(reinterpret_cast(d.input.data())); llarp_buffer_t buffer(input, input, d.input.size()); std::vector result; REQUIRE(llarp::bencode_read_dict( [&](llarp_buffer_t*, llarp_buffer_t* key) { if (key) { result.emplace_back(key->base, key->base + key->sz); } return true; }, &buffer)); REQUIRE(result == d.output); } std::vector listReadData{ {"le", {}}, {"l1:ae", {"a"}}, {"l1:be", {"b"}}, {"l1:b2:23e", {"b", "23"}}}; TEST_CASE("Read list", "[bencode]") { auto d = GENERATE(from_range(listReadData)); byte_t* input = const_cast(reinterpret_cast(d.input.data())); llarp_buffer_t buffer(input, input, d.input.size()); std::vector result; REQUIRE(llarp::bencode_read_list( [&](llarp_buffer_t* b, bool cont) { if (cont) { llarp_buffer_t tmp; bencode_read_string(b, &tmp); result.emplace_back(tmp.base, tmp.base + tmp.sz); } return true; }, &buffer)); REQUIRE(result == d.output); } TEST_CASE("Read dict to empty buffer", "[bencode]") { llarp_buffer_t buf((byte_t*)nullptr, 0); REQUIRE_FALSE( llarp::bencode_read_dict([](llarp_buffer_t*, llarp_buffer_t*) { return true; }, &buf)); }