refactor: saxlike benc pt. 2: add announce response parsing tests (#2505)

This commit is contained in:
Charles Kerr 2022-01-24 22:25:55 -06:00 committed by GitHub
parent 81066aae25
commit 064ad6a436
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 304 additions and 94 deletions

View File

@ -0,0 +1,21 @@
{
<tr_quark_strings>
Memcheck:Leak
match-leak-kinds: reachable
fun:malloc
fun:_Z9tr_mallocm
fun:_Z10tr_strvDupSt17basic_string_viewIcSt11char_traitsIcEE
fun:_Z12tr_quark_newSt17basic_string_viewIcSt11char_traitsIcEE
}
{
<tr_quark_vector>
Memcheck:Leak
match-leak-kinds: reachable
fun:_Znwm
fun:_ZN9__gnu_cxx13new_allocatorISt17basic_string_viewIcSt11char_traitsIcEEE8allocateEmPKv
fun:_ZNSt16allocator_traitsISaISt17basic_string_viewIcSt11char_traitsIcEEEE8allocateERS4_m
fun:_ZNSt12_Vector_baseISt17basic_string_viewIcSt11char_traitsIcEESaIS3_EE11_M_allocateEm
fun:_ZNSt6vectorISt17basic_string_viewIcSt11char_traitsIcEESaIS3_EE17_M_realloc_insertIJPcmEEEvN9__gnu_cxx17__normal_iteratorIPS3_S5_EEDpOT_
fun:_ZNSt6vectorISt17basic_string_viewIcSt11char_traitsIcEESaIS3_EE12emplace_backIJPcmEEERS3_DpOT_
fun:_Z12tr_quark_newSt17basic_string_viewIcSt11char_traitsIcEE
}

View File

@ -17,6 +17,7 @@
#include "transmission.h"
#include "interned-string.h"
#include "peer-mgr.h" // tr_pex
#include "web-utils.h"
/***
@ -169,8 +170,6 @@ struct tr_announce_request
char log_name[128];
};
struct tr_pex;
struct tr_announce_response
{
/* the torrent's info hash */
@ -232,4 +231,6 @@ void tr_tracker_udp_announce(
void tr_tracker_udp_start_shutdown(tr_session* session);
void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::string_view msg);
tr_interned_string tr_announcerGetKey(tr_url_parsed_t const& parsed);

View File

@ -6,6 +6,8 @@
#include <climits> /* USHRT_MAX */
#include <cstdio> /* fprintf() */
#include <cstring> /* strchr(), memcmp(), memcpy() */
#include <iostream>
#include <iomanip>
#include <string>
#include <string_view>
@ -15,7 +17,10 @@
#define LIBTRANSMISSION_ANNOUNCER_MODULE
#include "transmission.h"
#include "announcer-common.h"
#include "crypto-utils.h"
#include "error.h"
#include "log.h"
#include "net.h" /* tr_globalIPv6() */
#include "peer-mgr.h" /* pex */
@ -24,8 +29,8 @@
#include "trevent.h" /* tr_runInEventThread() */
#include "utils.h"
#include "variant.h"
#include "web.h"
#include "web-utils.h"
#include "web.h"
#define dbgmsg(name, ...) tr_logAddDeepNamed(name, __VA_ARGS__)
@ -189,6 +194,106 @@ static void on_announce_done_eventthread(void* vdata)
delete data;
}
static void maybeLogMessage(std::string_view description, tr_direction direction, std::string_view message)
{
auto& out = std::cerr;
static bool const verbose = tr_env_key_exists("TR_CURL_VERBOSE");
if (!verbose)
{
return;
}
auto const direction_sv = direction == TR_DOWN ? "<< "sv : ">> "sv;
out << description << std::endl << "[raw]"sv << direction_sv;
for (unsigned char ch : message)
{
if (isprint(ch))
{
out << ch;
}
else
{
out << "\\x"sv << std::hex << std::setw(2) << std::setfill('0') << unsigned(ch) << std::dec << std::setw(1)
<< std::setfill(' ');
}
}
out << std::endl << "[b64]"sv << direction_sv << tr_base64_encode(message) << std::endl;
}
void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::string_view msg)
{
maybeLogMessage("Announce response:", TR_DOWN, msg);
auto benc = tr_variant{};
auto const variant_loaded = tr_variantFromBuf(&benc, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, msg);
if (variant_loaded && tr_variantIsDict(&benc))
{
auto i = int64_t{};
auto sv = std::string_view{};
tr_variant* tmp = nullptr;
if (tr_variantDictFindStrView(&benc, TR_KEY_failure_reason, &sv))
{
response.errmsg = sv;
}
if (tr_variantDictFindStrView(&benc, TR_KEY_warning_message, &sv))
{
response.warning = sv;
}
if (tr_variantDictFindInt(&benc, TR_KEY_interval, &i))
{
response.interval = i;
}
if (tr_variantDictFindInt(&benc, TR_KEY_min_interval, &i))
{
response.min_interval = i;
}
if (tr_variantDictFindStrView(&benc, TR_KEY_tracker_id, &sv))
{
response.tracker_id = sv;
}
if (tr_variantDictFindInt(&benc, TR_KEY_complete, &i))
{
response.seeders = i;
}
if (tr_variantDictFindInt(&benc, TR_KEY_incomplete, &i))
{
response.leechers = i;
}
if (tr_variantDictFindInt(&benc, TR_KEY_downloaded, &i))
{
response.downloads = i;
}
if (tr_variantDictFindStrView(&benc, TR_KEY_peers6, &sv))
{
response.pex6 = tr_peerMgrCompact6ToPex(std::data(sv), std::size(sv), nullptr, 0);
}
if (tr_variantDictFindStrView(&benc, TR_KEY_peers, &sv))
{
response.pex = tr_peerMgrCompactToPex(std::data(sv), std::size(sv), nullptr, 0);
}
else if (tr_variantDictFindList(&benc, TR_KEY_peers, &tmp))
{
response.pex = listToPex(tmp);
}
}
if (variant_loaded)
{
tr_variantFree(&benc);
}
}
static void on_announce_done(
tr_session* session,
bool did_connect,
@ -211,94 +316,17 @@ static void on_announce_done(
}
else
{
tr_variant benc;
auto const variant_loaded = tr_variantFromBuf(&benc, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, msg);
tr_announcerParseHttpAnnounceResponse(*response, msg);
}
if (tr_env_key_exists("TR_CURL_VERBOSE"))
{
if (!variant_loaded)
{
fprintf(stderr, "%s", "Announce response was not in benc format\n");
}
else
{
fprintf(stderr, "%s", "Announce response:\n< ");
for (auto const ch : tr_variantToStr(&benc, TR_VARIANT_FMT_JSON))
{
fputc(ch, stderr);
}
fputc('\n', stderr);
}
}
if (!std::empty(response->pex6))
{
dbgmsg(data->log_name, "got a peers6 length of %zu", std::size(response->pex6));
}
if (variant_loaded && tr_variantIsDict(&benc))
{
auto i = int64_t{};
auto sv = std::string_view{};
tr_variant* tmp = nullptr;
if (tr_variantDictFindStrView(&benc, TR_KEY_failure_reason, &sv))
{
response->errmsg = sv;
}
if (tr_variantDictFindStrView(&benc, TR_KEY_warning_message, &sv))
{
response->warning = sv;
}
if (tr_variantDictFindInt(&benc, TR_KEY_interval, &i))
{
response->interval = i;
}
if (tr_variantDictFindInt(&benc, TR_KEY_min_interval, &i))
{
response->min_interval = i;
}
if (tr_variantDictFindStrView(&benc, TR_KEY_tracker_id, &sv))
{
response->tracker_id = sv;
}
if (tr_variantDictFindInt(&benc, TR_KEY_complete, &i))
{
response->seeders = i;
}
if (tr_variantDictFindInt(&benc, TR_KEY_incomplete, &i))
{
response->leechers = i;
}
if (tr_variantDictFindInt(&benc, TR_KEY_downloaded, &i))
{
response->downloads = i;
}
if (tr_variantDictFindStrView(&benc, TR_KEY_peers6, &sv))
{
dbgmsg(data->log_name, "got a peers6 length of %zu", std::size(sv));
response->pex6 = tr_peerMgrCompact6ToPex(std::data(sv), std::size(sv), nullptr, 0);
}
if (tr_variantDictFindStrView(&benc, TR_KEY_peers, &sv))
{
dbgmsg(data->log_name, "got a compact peers length of %zu", std::size(sv));
response->pex = tr_peerMgrCompactToPex(std::data(sv), std::size(sv), nullptr, 0);
}
else if (tr_variantDictFindList(&benc, TR_KEY_peers, &tmp))
{
response->pex = listToPex(tmp);
dbgmsg(data->log_name, "got a peers list with %zu entries", std::size(response->pex));
}
}
if (variant_loaded)
{
tr_variantFree(&benc);
}
if (!std::empty(response->pex))
{
dbgmsg(data->log_name, "got a peers length of %zu", std::size(response->pex));
}
tr_runInEventThread(session, on_announce_done_eventthread, data);
@ -310,10 +338,7 @@ void tr_tracker_http_announce(
tr_announce_response_func response_func,
void* response_func_user_data)
{
auto* const d = new announce_data{};
d->response.seeders = -1;
d->response.leechers = -1;
d->response.downloads = -1;
auto* const d = new announce_data();
d->response_func = response_func;
d->response_func_user_data = response_func_user_data;
d->response.info_hash = request->info_hash;
@ -490,7 +515,7 @@ void tr_tracker_http_scrape(
tr_scrape_response_func response_func,
void* response_func_user_data)
{
auto* d = new scrape_data{};
auto* d = new scrape_data();
d->response.scrape_url = request->scrape_url;
d->response_func = response_func;
d->response_func_user_data = response_func_user_data;

View File

@ -9,8 +9,10 @@
#error only libtransmission should #include this header.
#endif
#include <array>
#include <cinttypes> // uintX_t
#include <cstddef> // size_t
#include <string>
#include <vector>
#ifdef _WIN32
@ -56,6 +58,18 @@ struct tr_pex
tr_address addr;
tr_port port; /* this field is in network byte order */
uint8_t flags;
std::string_view to_string(char* buf, size_t buflen) const
{
tr_address_and_port_to_string(buf, buflen, &addr, port);
return buf;
}
[[nodiscard]] std::string to_string() const
{
auto buf = std::array<char, 64>{};
return std::string{ to_string(std::data(buf), std::size(buf)) };
}
};
constexpr bool tr_isPex(tr_pex const* pex)

View File

@ -1,5 +1,6 @@
add_executable(libtransmission-test
announce-list-test.cc
announcer-test.cc
bitfield-test.cc
block-info-test.cc
blocklist-test.cc

View File

@ -0,0 +1,148 @@
// This file Copyright (C) 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <array>
#include <string_view>
#define LIBTRANSMISSION_ANNOUNCER_MODULE
#include "transmission.h"
#include "announcer-common.h"
#include "crypto-utils.h"
#include "net.h"
#include "test-fixtures.h"
using AnnouncerTest = ::testing::Test;
using namespace std::literals;
TEST_F(AnnouncerTest, parseHttpAnnounceResponseNoPeers)
{
// clang-format off
auto constexpr NoPeers =
"d"
"8:complete" "i3e"
"10:downloaded" "i2e"
"10:incomplete" "i0e"
"8:interval" "i1803e"
"12:min interval" "i1800e"
"5:peers" "0:"
"e"sv;
// clang-format on
auto response = tr_announce_response{};
tr_announcerParseHttpAnnounceResponse(response, NoPeers);
EXPECT_EQ(1803, response.interval);
EXPECT_EQ(1800, response.min_interval);
EXPECT_EQ(3, response.seeders);
EXPECT_EQ(0, response.leechers);
EXPECT_EQ(2, response.downloads);
EXPECT_EQ(0, std::size(response.pex));
EXPECT_EQ(0, std::size(response.pex6));
EXPECT_EQ(""sv, response.errmsg);
EXPECT_EQ(""sv, response.warning);
}
TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexCompact)
{
// clang-format off
auto constexpr IPv4Peers =
"d"
"8:complete" "i3e"
"10:downloaded" "i2e"
"10:incomplete" "i0e"
"8:interval" "i1803e"
"12:min interval" "i1800e"
"5:peers"
"6:\x7F\x00\x00\x01\xfc\x27"
"e"sv;
// clang-format on
auto response = tr_announce_response{};
tr_announcerParseHttpAnnounceResponse(response, IPv4Peers);
EXPECT_EQ(1803, response.interval);
EXPECT_EQ(1800, response.min_interval);
EXPECT_EQ(3, response.seeders);
EXPECT_EQ(0, response.leechers);
EXPECT_EQ(2, response.downloads);
EXPECT_EQ(""sv, response.errmsg);
EXPECT_EQ(""sv, response.warning);
EXPECT_EQ(1, std::size(response.pex));
EXPECT_EQ(0, std::size(response.pex6));
if (std::size(response.pex) == 1)
{
EXPECT_EQ("[127.0.0.1]:64551"sv, response.pex[0].to_string());
}
}
TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexList)
{
// clang-format off
auto constexpr IPv4Peers =
"d"
"8:complete" "i3e"
"10:downloaded" "i2e"
"10:incomplete" "i0e"
"8:interval" "i1803e"
"12:min interval" "i1800e"
"5:peers"
"l"
"d"
"7:peer id" "20:-TR300Z-0123456789AB"
"2:ip" "7:8.8.4.4"
"4:port" "i53e"
"e"
"e"
"e"sv;
// clang-format on
auto response = tr_announce_response{};
tr_announcerParseHttpAnnounceResponse(response, IPv4Peers);
EXPECT_EQ(1803, response.interval);
EXPECT_EQ(1800, response.min_interval);
EXPECT_EQ(3, response.seeders);
EXPECT_EQ(0, response.leechers);
EXPECT_EQ(2, response.downloads);
EXPECT_EQ(""sv, response.errmsg);
EXPECT_EQ(""sv, response.warning);
EXPECT_EQ(1, std::size(response.pex));
EXPECT_EQ(0, std::size(response.pex6));
if (std::size(response.pex) == 1)
{
EXPECT_EQ("[8.8.4.4]:53"sv, response.pex[0].to_string());
}
}
TEST_F(AnnouncerTest, parseHttpAnnounceResponseFailureReason)
{
// clang-format off
auto constexpr NoPeers =
"d"
"8:complete" "i3e"
"14:failure reason" "6:foobar"
"10:downloaded" "i2e"
"10:incomplete" "i0e"
"8:interval" "i1803e"
"12:min interval" "i1800e"
"5:peers" "0:"
"e"sv;
// clang-format on
auto response = tr_announce_response{};
tr_announcerParseHttpAnnounceResponse(response, NoPeers);
EXPECT_EQ(1803, response.interval);
EXPECT_EQ(1800, response.min_interval);
EXPECT_EQ(3, response.seeders);
EXPECT_EQ(0, response.leechers);
EXPECT_EQ(2, response.downloads);
EXPECT_EQ(0, std::size(response.pex));
EXPECT_EQ(0, std::size(response.pex6));
EXPECT_EQ("foobar"sv, response.errmsg);
EXPECT_EQ(""sv, response.warning);
}