mirror of
https://github.com/transmission/transmission
synced 2025-01-30 10:52:00 +00:00
refactor: saxlike benc pt. 2: add announce response parsing tests (#2505)
This commit is contained in:
parent
81066aae25
commit
064ad6a436
6 changed files with 304 additions and 94 deletions
21
extras/libtransmission-valgrind.supp
Normal file
21
extras/libtransmission-valgrind.supp
Normal 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
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
148
tests/libtransmission/announcer-test.cc
Normal file
148
tests/libtransmission/announcer-test.cc
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue