mirror of
https://github.com/transmission/transmission
synced 2024-12-22 07:42:37 +00:00
2ff3ae07d1
* feat: accept ipv6 string in square brackets for `tr_address::from_string()` * test: add test case for ipv6 string in square brackets * fix: include square brackets in host component According to RFC3986: host = IP-literal / IPv4address / reg-name IP-literal = "[" ( IPv6address / IPvFuture ) "]" * fix: set ipv6-only socket before binding UDP socket Will return EINVAL on Linux otherwise * refactor: simplify code using `evutil` when binding TCP socket * fix: do not set SO_REUSEADDR for listening sockets on Windows systems Reason: https://stackoverflow.com/a/14388707/11390656 * fix: do not enclose ipv4 address string in square brackets
309 lines
9.8 KiB
C++
309 lines
9.8 KiB
C++
// This file Copyright (C) 2022 Mnemosyne LLC.
|
|
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
|
// or any future license endorsed by Mnemosyne LLC.
|
|
// License text can be found in the licenses/ folder.
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstddef> // std::byte
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
#define LIBTRANSMISSION_ANNOUNCER_MODULE
|
|
|
|
#include <libtransmission/announcer-common.h>
|
|
#include <libtransmission/net.h>
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
using AnnouncerTest = ::testing::Test;
|
|
|
|
using namespace std::literals;
|
|
|
|
static char const* const LogName = "LogName";
|
|
|
|
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:"
|
|
"11:external ip" "4:\x01\x02\x03\x04"
|
|
"e"sv;
|
|
// clang-format on
|
|
|
|
auto response = tr_announce_response{};
|
|
tr_announcerParseHttpAnnounceResponse(response, NoPeers, LogName);
|
|
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);
|
|
auto addr = tr_address::from_string("1.2.3.4");
|
|
EXPECT_TRUE(addr.has_value());
|
|
assert(addr.has_value());
|
|
EXPECT_EQ(*addr, response.external_ip);
|
|
EXPECT_EQ(0U, std::size(response.pex));
|
|
EXPECT_EQ(0U, 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, LogName);
|
|
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(1U, std::size(response.pex));
|
|
EXPECT_EQ(0U, std::size(response.pex6));
|
|
|
|
if (std::size(response.pex) == 1)
|
|
{
|
|
EXPECT_EQ("127.0.0.1:64551"sv, response.pex[0].display_name());
|
|
}
|
|
}
|
|
|
|
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, LogName);
|
|
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(1U, std::size(response.pex));
|
|
EXPECT_EQ(0U, std::size(response.pex6));
|
|
|
|
if (std::size(response.pex) == 1)
|
|
{
|
|
EXPECT_EQ("8.8.4.4:53"sv, response.pex[0].display_name());
|
|
}
|
|
}
|
|
|
|
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, LogName);
|
|
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(0U, std::size(response.pex));
|
|
EXPECT_EQ(0U, std::size(response.pex6));
|
|
EXPECT_EQ("foobar"sv, response.errmsg);
|
|
EXPECT_EQ(""sv, response.warning);
|
|
}
|
|
|
|
TEST_F(AnnouncerTest, parseHttpScrapeResponseMulti)
|
|
{
|
|
// clang-format off
|
|
auto constexpr ResponseBenc =
|
|
"d"
|
|
"5:files"
|
|
"d"
|
|
"20:aaaaaaaaaaaaaaaaaaaa"
|
|
"d"
|
|
"8:complete" "i1e"
|
|
"10:incomplete" "i2e"
|
|
"10:downloaded" "i3e"
|
|
"e"
|
|
|
|
"20:bbbbbbbbbbbbbbbbbbbb"
|
|
"d"
|
|
"8:complete" "i4e"
|
|
"10:incomplete" "i5e"
|
|
"10:downloaded" "i6e"
|
|
"e"
|
|
|
|
"20:cccccccccccccccccccc"
|
|
"d"
|
|
"8:complete" "i7e"
|
|
"10:incomplete" "i8e"
|
|
"10:downloaded" "i9e"
|
|
"e"
|
|
"e"
|
|
"e"sv;
|
|
// clang-format on
|
|
|
|
auto response = tr_scrape_response{};
|
|
std::fill_n(std::data(response.rows[0].info_hash), std::size(response.rows[0].info_hash), std::byte{ 'a' });
|
|
std::fill_n(std::data(response.rows[1].info_hash), std::size(response.rows[1].info_hash), std::byte{ 'b' });
|
|
std::fill_n(std::data(response.rows[2].info_hash), std::size(response.rows[2].info_hash), std::byte{ 'c' });
|
|
response.row_count = 3;
|
|
tr_announcerParseHttpScrapeResponse(response, ResponseBenc, LogName);
|
|
|
|
EXPECT_EQ(1, response.rows[0].seeders);
|
|
EXPECT_EQ(2, response.rows[0].leechers);
|
|
EXPECT_EQ(3, response.rows[0].downloads);
|
|
|
|
EXPECT_EQ(4, response.rows[1].seeders);
|
|
EXPECT_EQ(5, response.rows[1].leechers);
|
|
EXPECT_EQ(6, response.rows[1].downloads);
|
|
|
|
EXPECT_EQ(7, response.rows[2].seeders);
|
|
EXPECT_EQ(8, response.rows[2].leechers);
|
|
EXPECT_EQ(9, response.rows[2].downloads);
|
|
}
|
|
|
|
TEST_F(AnnouncerTest, parseHttpScrapeResponseMultiWithUnexpected)
|
|
{
|
|
// clang-format off
|
|
auto constexpr ResponseBenc =
|
|
"d"
|
|
"5:files"
|
|
"d"
|
|
"20:aaaaaaaaaaaaaaaaaaaa"
|
|
"d"
|
|
"8:complete" "i1e"
|
|
"10:incomplete" "i2e"
|
|
"10:downloaded" "i3e"
|
|
"e"
|
|
|
|
"20:bbbbbbbbbbbbbbbbbbbb"
|
|
"d"
|
|
"8:complete" "i4e"
|
|
"10:incomplete" "i5e"
|
|
"10:downloaded" "i6e"
|
|
"e"
|
|
|
|
"20:cccccccccccccccccccc"
|
|
"d"
|
|
"8:complete" "i7e"
|
|
"10:incomplete" "i8e"
|
|
"10:downloaded" "i9e"
|
|
"e"
|
|
|
|
"20:dddddddddddddddddddd"
|
|
"d"
|
|
"8:complete" "i7e"
|
|
"10:incomplete" "i8e"
|
|
"10:downloaded" "i9e"
|
|
"e"
|
|
"e"
|
|
"e"sv;
|
|
// clang-format on
|
|
|
|
auto response = tr_scrape_response{};
|
|
std::fill_n(std::data(response.rows[0].info_hash), std::size(response.rows[0].info_hash), std::byte{ 'a' });
|
|
std::fill_n(std::data(response.rows[1].info_hash), std::size(response.rows[1].info_hash), std::byte{ 'b' });
|
|
std::fill_n(std::data(response.rows[2].info_hash), std::size(response.rows[2].info_hash), std::byte{ 'c' });
|
|
response.row_count = 3;
|
|
tr_announcerParseHttpScrapeResponse(response, ResponseBenc, LogName);
|
|
|
|
EXPECT_EQ(1, response.rows[0].seeders);
|
|
EXPECT_EQ(2, response.rows[0].leechers);
|
|
EXPECT_EQ(3, response.rows[0].downloads);
|
|
|
|
EXPECT_EQ(4, response.rows[1].seeders);
|
|
EXPECT_EQ(5, response.rows[1].leechers);
|
|
EXPECT_EQ(6, response.rows[1].downloads);
|
|
|
|
EXPECT_EQ(7, response.rows[2].seeders);
|
|
EXPECT_EQ(8, response.rows[2].leechers);
|
|
EXPECT_EQ(9, response.rows[2].downloads);
|
|
}
|
|
|
|
TEST_F(AnnouncerTest, parseHttpScrapeResponseMultiWithMissing)
|
|
{
|
|
// clang-format off
|
|
auto constexpr ResponseBenc =
|
|
"d"
|
|
"5:files"
|
|
"d"
|
|
"20:aaaaaaaaaaaaaaaaaaaa"
|
|
"d"
|
|
"8:complete" "i1e"
|
|
"10:incomplete" "i2e"
|
|
"10:downloaded" "i3e"
|
|
"e"
|
|
|
|
"20:cccccccccccccccccccc"
|
|
"d"
|
|
"8:complete" "i7e"
|
|
"10:incomplete" "i8e"
|
|
"10:downloaded" "i9e"
|
|
"e"
|
|
"e"
|
|
"e"sv;
|
|
// clang-format on
|
|
|
|
auto response = tr_scrape_response{};
|
|
std::fill_n(std::data(response.rows[0].info_hash), std::size(response.rows[0].info_hash), std::byte{ 'a' });
|
|
std::fill_n(std::data(response.rows[1].info_hash), std::size(response.rows[1].info_hash), std::byte{ 'b' });
|
|
std::fill_n(std::data(response.rows[2].info_hash), std::size(response.rows[2].info_hash), std::byte{ 'c' });
|
|
response.row_count = 3;
|
|
tr_announcerParseHttpScrapeResponse(response, ResponseBenc, LogName);
|
|
|
|
EXPECT_EQ(1, response.rows[0].seeders);
|
|
EXPECT_EQ(2, response.rows[0].leechers);
|
|
EXPECT_EQ(3, response.rows[0].downloads);
|
|
|
|
EXPECT_EQ(std::nullopt, response.rows[1].seeders);
|
|
EXPECT_EQ(std::nullopt, response.rows[1].leechers);
|
|
EXPECT_EQ(std::nullopt, response.rows[1].downloads);
|
|
|
|
EXPECT_EQ(7, response.rows[2].seeders);
|
|
EXPECT_EQ(8, response.rows[2].leechers);
|
|
EXPECT_EQ(9, response.rows[2].downloads);
|
|
}
|