diff --git a/extras/libtransmission-valgrind.supp b/extras/libtransmission-valgrind.supp new file mode 100644 index 000000000..37f917d1b --- /dev/null +++ b/extras/libtransmission-valgrind.supp @@ -0,0 +1,21 @@ +{ + + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:_Z9tr_mallocm + fun:_Z10tr_strvDupSt17basic_string_viewIcSt11char_traitsIcEE + fun:_Z12tr_quark_newSt17basic_string_viewIcSt11char_traitsIcEE +} +{ + + 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 +} diff --git a/libtransmission/announcer-common.h b/libtransmission/announcer-common.h index 5b7ac32a9..fd02c08e9 100644 --- a/libtransmission/announcer-common.h +++ b/libtransmission/announcer-common.h @@ -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); diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index 93117a640..a6357fdf6 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -6,6 +6,8 @@ #include /* USHRT_MAX */ #include /* fprintf() */ #include /* strchr(), memcmp(), memcpy() */ +#include +#include #include #include @@ -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; diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index 5947a2d3a..6ace16a16 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -9,8 +9,10 @@ #error only libtransmission should #include this header. #endif +#include #include // uintX_t #include // size_t +#include #include #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{}; + return std::string{ to_string(std::data(buf), std::size(buf)) }; + } }; constexpr bool tr_isPex(tr_pex const* pex) diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 99e37977e..859f704a9 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -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 diff --git a/tests/libtransmission/announcer-test.cc b/tests/libtransmission/announcer-test.cc new file mode 100644 index 000000000..9e603a192 --- /dev/null +++ b/tests/libtransmission/announcer-test.cc @@ -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 +#include + +#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); +}