refactor: convert `tr_incomplete_metadata` to c++ class (#6383)

* refactor: unset peer BEP-9 support if size hint is invalid

* fix: open torrent file in binary mode

* refactor: move metadata size check to method

* refactor: remove duplicate checks

* refactor: reduce temp variable scope in `parseLtepHandshake()`

* refactor: convert `get_piece_length()` to method

* refactor: convert `tr_torrentSetMetadataSizeHint()` to method

* refactor: convert `tr_torrentGetMetadataPiece()` to method

* refactor: convert `tr_torrentUseMetainfoFromFile()` to method

* refactor: convert `tr_torrentSetMetadataPiece()` to method

* refactor: convert `tr_torrentGetNextMetadataRequest()` to method

* refactor: convert `tr_torrentGetMetadataPercent()` to method

* refactor: add basic framework for MagnetMediator

* refactor: initialise `tr_incomplete_metadata` fields in constructor

* refactor: check metadata transfer completion in `set_metadata_piece()`

* refactor: convert `use_new_metainfo()` and `on_have_all_metainfo()` to methods

* refactor: move parts of `tr_torrent::set_metadata_piece()` into `tr_incomplete_metadata`

* refactor: move parts of `tr_torrent::get_next_metadata_request()` into `tr_incomplete_metadata`

* refactor: move parts of `tr_torrent::get_metadata_percent()` into `tr_incomplete_metadata`

* refactor: hide all `tr_incomplete_metadata` fields

* refactor: move `incomplete_metadata` to private

* feat: add test for `set_metadata_piece()`

* refactor: unify integer types

* refactor: rename `tr_incomplete_metadata` to `tr_metadata_download`

* chore: make clang-tidy happy

libtransmission/torrent-magnet.cc:117:68: warning: comparison of integers of different signs: 'long' and 'const uint64_t' (aka 'const unsigned long') [clang-diagnostic-sign-compare]

* refactor: pass log name to `tr_metadata_download` constructor

* chore: iwyu

* fix: thread-safe `TorrentMagnetTest.setMetadataPiece`

* chore: housekeeping

* Revert "fix: thread-safe `TorrentMagnetTest.setMetadataPiece`"

This reverts commit 2a7fcd93a262888f9f55d542b1a9a2da9ca72cea.

* fix: stop soon instead of stop now in `on_metainfo_completed()`

This is unreachable code now, but if it is ever reached, Transmission
will very likely crash.

* fix: maybe fix OpenBSD test failure
This commit is contained in:
Yat Ho 2024-01-03 11:04:17 +08:00 committed by GitHub
parent 5000edef01
commit 10d047005a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 302 additions and 218 deletions

View File

@ -2256,7 +2256,6 @@ void tr_peerMgr::bandwidth_pulse()
for (auto* const tor : torrents_)
{
tor->do_idle_work();
tr_torrentMagnetDoIdleWork(tor);
}
reconnect_pulse();

View File

@ -1015,12 +1015,11 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
logtrace(msgs, fmt::format(FMT_STRING("here is the base64-encoded handshake: [{:s}]"), tr_base64_encode(handshake_sv)));
/* does the peer prefer encrypted connections? */
auto i = int64_t{};
auto pex = tr_pex{};
auto& [addr, port] = pex.socket_address;
if (tr_variantDictFindInt(&*var, TR_KEY_e, &i))
if (auto e = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_e, &e))
{
msgs->encryption_preference = i != 0 ? EncryptionPreference::Yes : EncryptionPreference::No;
msgs->encryption_preference = e != 0 ? EncryptionPreference::Yes : EncryptionPreference::No;
if (msgs->encryption_preference == EncryptionPreference::Yes)
{
@ -1034,21 +1033,21 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
if (tr_variant* sub = nullptr; tr_variantDictFindDict(&*var, TR_KEY_m, &sub))
{
if (tr_variantDictFindInt(sub, TR_KEY_ut_pex, &i))
if (auto ut_pex = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_pex, &ut_pex))
{
msgs->peerSupportsPex = i != 0;
msgs->ut_pex_id = static_cast<uint8_t>(i);
msgs->peerSupportsPex = ut_pex != 0;
msgs->ut_pex_id = static_cast<uint8_t>(ut_pex);
logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_pex is {:d}"), static_cast<int>(msgs->ut_pex_id)));
}
if (tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &i))
if (auto ut_metadata = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &ut_metadata))
{
msgs->peerSupportsMetadataXfer = i != 0;
msgs->ut_metadata_id = static_cast<uint8_t>(i);
msgs->peerSupportsMetadataXfer = ut_metadata != 0;
msgs->ut_metadata_id = static_cast<uint8_t>(ut_metadata);
logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_metadata_id is {:d}"), static_cast<int>(msgs->ut_metadata_id)));
}
if (tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &i))
if (auto ut_holepunch = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &ut_holepunch))
{
// Transmission doesn't support this extension yet.
// But its presence does indicate µTP supports,
@ -1058,13 +1057,20 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
}
/* look for metainfo size (BEP 9) */
if (tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &i))
if (auto metadata_size = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &metadata_size))
{
tr_torrentSetMetadataSizeHint(msgs->torrent, i);
if (!tr_metadata_download::is_valid_metadata_size(metadata_size))
{
msgs->peerSupportsMetadataXfer = false;
}
else
{
msgs->torrent->maybe_start_metadata_transfer(metadata_size);
}
}
/* look for upload_only (BEP 21) */
if (tr_variantDictFindInt(&*var, TR_KEY_upload_only, &i))
if (auto upload_only = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_upload_only, &upload_only))
{
pex.flags |= ADDED_F_SEED_FLAG;
}
@ -1079,11 +1085,11 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
}
/* get peer's listening port */
if (tr_variantDictFindInt(&*var, TR_KEY_p, &i) && i > 0)
if (auto p = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_p, &p) && p > 0)
{
port.set_host(i);
port.set_host(p);
msgs->publish(tr_peer_event::GotPort(port));
logtrace(msgs, fmt::format(FMT_STRING("peer's port is now {:d}"), i));
logtrace(msgs, fmt::format(FMT_STRING("peer's port is now {:d}"), p));
}
std::byte const* addr_compact = nullptr;
@ -1103,9 +1109,9 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
}
/* get peer's maximum request queue size */
if (tr_variantDictFindInt(&*var, TR_KEY_reqq, &i))
if (auto reqq = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_reqq, &reqq))
{
msgs->reqq = i;
msgs->reqq = reqq;
}
}
@ -1135,13 +1141,10 @@ void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in)
/* NOOP */
}
auto const* const benc_end = serde.end();
if (msg_type == MetadataMsgType::Data && !msgs->torrent->has_metainfo() && msg_end - benc_end <= MetadataPieceSize &&
piece * MetadataPieceSize + (msg_end - benc_end) <= total_size)
if (auto const piece_len = msg_end - serde.end();
msg_type == MetadataMsgType::Data && piece * MetadataPieceSize + piece_len <= total_size)
{
size_t const piece_len = msg_end - benc_end;
tr_torrentSetMetadataPiece(msgs->torrent, piece, benc_end, piece_len);
msgs->torrent->set_metadata_piece(piece, serde.end(), piece_len);
}
if (msg_type == MetadataMsgType::Request)
@ -1745,7 +1748,7 @@ void updateMetadataRequests(tr_peerMsgsImpl* msgs, time_t now)
return;
}
if (auto const piece = tr_torrentGetNextMetadataRequest(msgs->torrent, now); piece)
if (auto const piece = msgs->torrent->get_next_metadata_request(now); piece)
{
auto tmp = tr_variant{};
tr_variantInitDict(&tmp, 3);
@ -1796,8 +1799,8 @@ namespace peer_pulse_helpers
return {};
}
auto data = tr_metadata_piece{};
if (!tr_torrentGetMetadataPiece(msgs->torrent, *piece, data))
auto data = msgs->torrent->get_metadata_piece(*piece);
if (!data)
{
// send a reject
auto tmp = tr_variant{};
@ -1813,7 +1816,7 @@ namespace peer_pulse_helpers
tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Data);
tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece);
tr_variantDictAddInt(&tmp, TR_KEY_total_size, msgs->torrent->info_dict_size());
return protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp), data);
return protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp), *data);
}
[[nodiscard]] size_t add_next_piece(tr_peerMsgsImpl* msgs, uint64_t now)

View File

@ -4,11 +4,9 @@
// License text can be found in the licenses/ folder.
#include <algorithm>
#include <climits> /* INT_MAX */
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <deque>
#include <fstream>
#include <ios>
#include <memory>
@ -16,7 +14,6 @@
#include <string>
#include <string_view>
#include <utility> // std::move
#include <vector>
#include <fmt/core.h>
@ -33,120 +30,114 @@
#include "libtransmission/utils.h"
#include "libtransmission/variant.h"
#define tr_logAddDebugMagnet(magnet, msg) tr_logAddDebug(msg, (magnet)->log_name())
namespace
{
// don't ask for the same metadata piece more than this often
auto constexpr MinRepeatIntervalSecs = int{ 3 };
auto constexpr MinRepeatIntervalSecs = time_t{ 3 };
[[nodiscard]] auto create_all_needed(int n_pieces)
[[nodiscard]] int64_t div_ceil(int64_t numerator, int64_t denominator)
{
auto ret = std::deque<tr_incomplete_metadata::metadata_node>{};
ret.resize(n_pieces);
for (int i = 0; i < n_pieces; ++i)
{
ret[i].piece = i;
}
return ret;
}
[[nodiscard]] int div_ceil(int numerator, int denominator)
{
auto const [quot, rem] = std::div(numerator, denominator);
auto const [quot, rem] = std::lldiv(numerator, denominator);
return quot + (rem == 0 ? 0 : 1);
}
} // namespace
bool tr_torrentSetMetadataSizeHint(tr_torrent* tor, int64_t size)
void tr_metadata_download::create_all_needed(int64_t n_pieces) noexcept
{
if (tor->has_metainfo())
pieces_needed_.clear();
pieces_needed_.resize(n_pieces);
for (int64_t i = 0; i < n_pieces; ++i)
{
return false;
pieces_needed_[i].piece = i;
}
if (tor->incomplete_metadata)
{
return false;
}
int const n = (size <= 0 || size > INT_MAX) ? -1 : div_ceil(size, MetadataPieceSize);
tr_logAddDebugTor(tor, fmt::format("metadata is {} bytes in {} pieces", size, n));
if (n <= 0)
{
return false;
}
auto m = std::make_unique<tr_incomplete_metadata>();
m->piece_count = n;
m->metadata.resize(size);
m->pieces_needed = create_all_needed(n);
if (std::empty(m->metadata) || std::empty(m->pieces_needed))
{
return false;
}
tor->incomplete_metadata = std::move(m);
return true;
}
bool tr_torrentGetMetadataPiece(tr_torrent const* tor, int piece, tr_metadata_piece& setme)
tr_metadata_download::tr_metadata_download(std::string_view log_name, int64_t const size)
: log_name_{ std::string{ log_name } }
{
TR_ASSERT(is_valid_metadata_size(size));
auto const n = div_ceil(size, MetadataPieceSize);
tr_logAddDebugMagnet(this, fmt::format("metadata is {} bytes in {} pieces", size, n));
piece_count_ = n;
metadata_.resize(size);
create_all_needed(n);
}
void tr_torrent::maybe_start_metadata_transfer(int64_t const size) noexcept
{
if (has_metainfo() || metadata_download_)
{
return;
}
if (!tr_metadata_download::is_valid_metadata_size(size))
{
TR_ASSERT(false);
return;
}
metadata_download_ = std::make_unique<tr_metadata_download>(name(), size);
}
[[nodiscard]] std::optional<tr_metadata_piece> tr_torrent::get_metadata_piece(int64_t const piece) const
{
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(piece >= 0);
if (!tor->has_metainfo())
if (!has_metainfo())
{
return {};
}
auto const n_pieces = std::max(1, div_ceil(tor->info_dict_size(), MetadataPieceSize));
if (piece < 0 || piece >= n_pieces)
auto const info_dict_size = this->info_dict_size();
TR_ASSERT(info_dict_size > 0);
if (auto const n_pieces = std::max(int64_t{ 1 }, div_ceil(info_dict_size, MetadataPieceSize));
piece < 0 || piece >= n_pieces)
{
return {};
}
auto in = std::ifstream{ tor->torrent_file(), std::ios_base::in };
auto in = std::ifstream{ torrent_file(), std::ios_base::in | std::ios_base::binary };
if (!in.is_open())
{
return {};
}
auto const info_dict_size = tor->info_dict_size();
TR_ASSERT(info_dict_size > 0);
auto const offset_in_info_dict = static_cast<uint64_t>(piece) * MetadataPieceSize;
if (auto const offset_in_file = tor->info_dict_offset() + offset_in_info_dict; !in.seekg(offset_in_file))
auto const offset_in_info_dict = piece * MetadataPieceSize;
if (auto const offset_in_file = info_dict_offset() + offset_in_info_dict; !in.seekg(offset_in_file))
{
return {};
}
auto const piece_len = offset_in_info_dict + MetadataPieceSize <= info_dict_size ? MetadataPieceSize :
info_dict_size - offset_in_info_dict;
setme.resize(piece_len);
return !!in.read(reinterpret_cast<char*>(std::data(setme)), std::size(setme));
auto const piece_len = static_cast<size_t>(offset_in_info_dict + MetadataPieceSize) <= info_dict_size ?
MetadataPieceSize :
info_dict_size - offset_in_info_dict;
if (auto ret = tr_metadata_piece(piece_len); in.read(reinterpret_cast<char*>(std::data(ret)), std::size(ret)))
{
return ret;
}
return {};
}
bool tr_torrentUseMetainfoFromFile(
tr_torrent* tor,
tr_torrent_metainfo const* metainfo,
char const* filename_in,
tr_error* error)
bool tr_torrent::use_metainfo_from_file(tr_torrent_metainfo const* metainfo, char const* filename_in, tr_error* error)
{
// add .torrent file
if (!tr_sys_path_copy(filename_in, tor->torrent_file().c_str(), error))
if (!tr_sys_path_copy(filename_in, torrent_file().c_str(), error))
{
return false;
}
// remove .magnet file
tr_sys_path_remove(tor->magnet_file());
tr_sys_path_remove(magnet_file());
// tor should keep this metainfo
tor->set_metainfo(*metainfo);
set_metainfo(*metainfo);
tor->incomplete_metadata.reset();
metadata_download_.reset();
return true;
}
@ -157,13 +148,6 @@ namespace
{
namespace set_metadata_piece_helpers
{
[[nodiscard]] constexpr size_t get_piece_length(tr_incomplete_metadata const& m, int piece)
{
return piece + 1 == m.piece_count ? // last piece
std::size(m.metadata) - (piece * MetadataPieceSize) :
MetadataPieceSize;
}
tr_variant build_metainfo_except_info_dict(tr_torrent_metainfo const& tm)
{
auto top = tr_variant::Map{ 8U };
@ -205,21 +189,25 @@ tr_variant build_metainfo_except_info_dict(tr_torrent_metainfo const& tm)
return tr_variant{ std::move(top) };
}
} // namespace set_metadata_piece_helpers
} // namespace
bool use_new_metainfo(tr_torrent* tor, tr_error* error)
[[nodiscard]] bool tr_torrent::use_new_metainfo(tr_error* error)
{
auto const& m = tor->incomplete_metadata;
using namespace set_metadata_piece_helpers;
auto const& m = metadata_download_;
TR_ASSERT(m);
// test the info_dict checksum
if (tr_sha1::digest(m->metadata) != tor->info_hash())
if (tr_sha1::digest(m->get_metadata()) != info_hash())
{
return false;
}
// checksum passed; now try to parse it as benc
auto serde = tr_variant_serde::benc().inplace();
auto info_dict_v = serde.parse(m->metadata);
auto info_dict_v = serde.parse(m->get_metadata());
if (!info_dict_v)
{
if (error != nullptr)
@ -232,7 +220,7 @@ bool use_new_metainfo(tr_torrent* tor, tr_error* error)
}
// yay we have an info dict. Let's make a torrent file
auto top_var = build_metainfo_except_info_dict(tor->metainfo());
auto top_var = build_metainfo_except_info_dict(metainfo());
tr_variantMergeDicts(tr_variantDictAddDict(&top_var, TR_KEY_info, 0), &*info_dict_v);
auto const benc = serde.to_string(top_var);
@ -244,111 +232,90 @@ bool use_new_metainfo(tr_torrent* tor, tr_error* error)
}
// save it
if (!tr_file_save(tor->torrent_file(), benc, error))
if (!tr_file_save(torrent_file(), benc, error))
{
return false;
}
// remove .magnet file
tr_sys_path_remove(tor->magnet_file());
tr_sys_path_remove(magnet_file());
// tor should keep this metainfo
tor->set_metainfo(metainfo);
set_metainfo(metainfo);
return true;
}
void on_have_all_metainfo(tr_torrent* tor)
void tr_torrent::on_have_all_metainfo()
{
auto error = tr_error{};
auto& m = tor->incomplete_metadata;
auto& m = metadata_download_;
TR_ASSERT(m);
if (!use_new_metainfo(tor, &error)) /* drat. */
if (auto error = tr_error{}; !use_new_metainfo(&error)) /* drat. */
{
auto msg = std::string_view{ error && !std::empty(error.message()) ? error.message() : "unknown error" };
tr_logAddWarnTor(
tor,
this,
fmt::format("Couldn't parse magnet metainfo: '{error}'. Redownloading metadata", fmt::arg("error", msg)));
}
m.reset();
}
} // namespace set_metadata_piece_helpers
} // namespace
void tr_torrentMagnetDoIdleWork(tr_torrent* const tor)
bool tr_metadata_download::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
{
using namespace set_metadata_piece_helpers;
TR_ASSERT(tr_isTorrent(tor));
if (auto const& m = tor->incomplete_metadata; m && std::empty(m->pieces_needed))
{
tr_logAddDebugTor(tor, fmt::format("we now have all the metainfo!"));
on_have_all_metainfo(tor);
}
}
void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, size_t len)
{
using namespace set_metadata_piece_helpers;
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(data != nullptr);
tr_logAddDebugTor(tor, fmt::format("got metadata piece {} of {} bytes", piece, len));
// are we set up to download metadata?
auto& m = tor->incomplete_metadata;
if (!m)
{
return;
}
// sanity test: is `piece` in range?
if ((piece < 0) || (piece >= m->piece_count))
if (piece < 0 || piece >= piece_count_)
{
return;
return false;
}
// sanity test: is `len` the right size?
if (get_piece_length(*m, piece) != len)
if (get_piece_length(piece) != len)
{
return;
return false;
}
// do we need this piece?
auto& needed = m->pieces_needed;
auto& needed = pieces_needed_;
auto const iter = std::find_if(
std::begin(needed),
std::end(needed),
[piece](auto const& item) { return item.piece == piece; });
if (iter == std::end(needed))
{
return;
return false;
}
size_t const offset = piece * MetadataPieceSize;
std::copy_n(reinterpret_cast<char const*>(data), len, std::begin(m->metadata) + offset);
auto const offset = piece * MetadataPieceSize;
std::copy_n(reinterpret_cast<char const*>(data), len, std::begin(metadata_) + offset);
needed.erase(iter);
tr_logAddDebugTor(tor, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed)));
tr_logAddDebugMagnet(this, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed)));
return std::empty(needed);
}
void tr_torrent::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
{
TR_ASSERT(data != nullptr);
tr_logAddDebugTor(this, fmt::format("got metadata piece {} of {} bytes", piece, len));
if (auto& m = metadata_download_; m && m->set_metadata_piece(piece, data, len))
{
tr_logAddDebugTor(this, fmt::format("we now have all the metainfo!"));
on_have_all_metainfo();
}
}
// ---
std::optional<int> tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now)
[[nodiscard]] std::optional<int64_t> tr_metadata_download::get_next_metadata_request(time_t const now) noexcept
{
TR_ASSERT(tr_isTorrent(tor));
auto& m = tor->incomplete_metadata;
if (!m)
{
return {};
}
auto& needed = m->pieces_needed;
auto& needed = pieces_needed_;
if (std::empty(needed) || needed.front().requested_at + MinRepeatIntervalSecs >= now)
{
return {};
@ -358,28 +325,47 @@ std::optional<int> tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now)
needed.pop_front();
req.requested_at = now;
needed.push_back(req);
tr_logAddDebugTor(tor, fmt::format("next piece to request: {}", req.piece));
tr_logAddDebugMagnet(this, fmt::format("next piece to request: {}", req.piece));
return req.piece;
}
double tr_torrentGetMetadataPercent(tr_torrent const* tor)
[[nodiscard]] std::optional<int64_t> tr_torrent::get_next_metadata_request(time_t const now) noexcept
{
if (tor->has_metainfo())
if (auto& m = metadata_download_; m)
{
return 1.0;
return m->get_next_metadata_request(now);
}
if (auto const& m = tor->incomplete_metadata; m)
return {};
}
[[nodiscard]] double tr_metadata_download::get_metadata_percent() const noexcept
{
if (auto const n = piece_count_; n != 0)
{
if (auto const n = m->piece_count; n != 0)
{
return (n - std::size(m->pieces_needed)) / static_cast<double>(n);
}
return (n - std::size(pieces_needed_)) / static_cast<double>(n);
}
return 0.0;
}
[[nodiscard]] double tr_torrent::get_metadata_percent() const noexcept
{
if (has_metainfo())
{
return 1.0;
}
if (auto const& m = metadata_download_; m)
{
return m->get_metadata_percent();
}
return 0.0;
}
// ---
std::string tr_torrentGetMagnetLink(tr_torrent const* tor)
{
return tor->magnet();

View File

@ -13,46 +13,67 @@
#include <cstdint> // int64_t
#include <ctime> // time_t
#include <deque>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <small/vector.hpp>
struct tr_error;
struct tr_torrent;
struct tr_torrent_metainfo;
#include "libtransmission/tr-macros.h"
// defined by BEP #9
inline constexpr int MetadataPieceSize = 1024 * 16;
inline constexpr auto MetadataPieceSize = 1024 * 16;
using tr_metadata_piece = small::max_size_vector<std::byte, MetadataPieceSize>;
struct tr_incomplete_metadata
class tr_metadata_download
{
public:
tr_metadata_download(std::string_view log_name, int64_t size);
[[nodiscard]] static constexpr auto is_valid_metadata_size(int64_t const size) noexcept
{
return size > 0 && size <= std::numeric_limits<int>::max();
}
bool set_metadata_piece(int64_t piece, void const* data, size_t len);
[[nodiscard]] std::optional<int64_t> get_next_metadata_request(time_t now) noexcept;
[[nodiscard]] double get_metadata_percent() const noexcept;
[[nodiscard]] constexpr auto const& get_metadata() const noexcept
{
return metadata_;
}
[[nodiscard]] TR_CONSTEXPR20 std::string_view log_name() const noexcept
{
return log_name_;
}
private:
struct metadata_node
{
time_t requested_at = 0U;
int piece = 0;
time_t requested_at = {};
int64_t piece = {};
};
std::vector<char> metadata;
[[nodiscard]] constexpr size_t get_piece_length(int64_t const piece) const noexcept
{
return piece + 1 == piece_count_ ? // last piece
std::size(metadata_) - (piece * MetadataPieceSize) :
MetadataPieceSize;
}
/** sorted from least to most recently requested */
std::deque<metadata_node> pieces_needed;
void create_all_needed(int64_t n_pieces) noexcept;
int piece_count = 0;
std::vector<char> metadata_;
std::deque<metadata_node> pieces_needed_;
int64_t piece_count_ = {};
std::string log_name_;
};
bool tr_torrentGetMetadataPiece(tr_torrent const* tor, int piece, tr_metadata_piece& setme);
void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, size_t len);
std::optional<int> tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now);
bool tr_torrentSetMetadataSizeHint(tr_torrent* tor, int64_t metadata_size);
double tr_torrentGetMetadataPercent(tr_torrent const* tor);
void tr_torrentMagnetDoIdleWork(tr_torrent* tor);
bool tr_torrentUseMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* metainfo, char const* filename, tr_error* error);

View File

@ -131,7 +131,7 @@ bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* m
}
auto error = tr_error{};
tr_torrentUseMetainfoFromFile(tor, metainfo, filename, &error);
tor->use_metainfo_from_file(metainfo, filename, &error);
if (error)
{
tor->error().set_local_error(fmt::format(
@ -890,7 +890,7 @@ void tr_torrent::on_metainfo_completed()
// Potentially, we are in `tr_torrent::init`,
// and we don't want any file created before `tr_torrent::start`
// so we Verify but we don't Create files.
tr_torrentVerify(this);
session->queue_session_thread(tr_torrentVerify, this);
}
else
{
@ -904,7 +904,7 @@ void tr_torrent::on_metainfo_completed()
}
else if (is_running())
{
tr_torrentStop(this);
stop_soon();
}
}
}
@ -1315,7 +1315,7 @@ tr_stat tr_torrent::stats() const
stats.pieceDownloadSpeed_KBps = piece_download_speed.count(Speed::Units::KByps);
stats.percentComplete = this->completion_.percent_complete();
stats.metadataPercentComplete = tr_torrentGetMetadataPercent(this);
stats.metadataPercentComplete = get_metadata_percent();
stats.percentDone = this->completion_.percent_done();
stats.leftUntilDone = this->completion_.left_until_done();

View File

@ -148,7 +148,6 @@ struct tr_torrent final : public tr_completion::torrent_view
uint64_t cur_ = {};
};
public:
using labels_t = std::vector<tr_interned_string>;
using VerifyDoneCallback = std::function<void(tr_torrent*)>;
@ -603,6 +602,18 @@ public:
[[nodiscard]] bool ensure_piece_is_checked(tr_piece_index_t piece);
/// METAINFO - MAGNET
void maybe_start_metadata_transfer(int64_t size) noexcept;
[[nodiscard]] std::optional<tr_metadata_piece> get_metadata_piece(int64_t piece) const;
void set_metadata_piece(int64_t piece, void const* data, size_t len);
[[nodiscard]] std::optional<int64_t> get_next_metadata_request(time_t now) noexcept;
[[nodiscard]] double get_metadata_percent() const noexcept;
///
[[nodiscard]] tr_stat stats() const;
@ -949,11 +960,6 @@ public:
CumulativeCount bytes_downloaded_;
CumulativeCount bytes_uploaded_;
/* Used when the torrent has been created with a magnet link
* and we're in the process of downloading the metainfo from
* other peers */
std::unique_ptr<tr_incomplete_metadata> incomplete_metadata;
tr_session* session = nullptr;
tr_torrent_announcer* torrent_announcer = nullptr;
@ -963,6 +969,7 @@ public:
time_t lpdAnnounceAt = 0;
private:
friend bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* metainfo, char const* filename);
friend tr_file_view tr_torrentFile(tr_torrent const* tor, tr_file_index_t file);
friend tr_stat const* tr_torrentStat(tr_torrent* tor);
friend tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of);
@ -1200,6 +1207,8 @@ private:
return fpm_.byte_span_for_file(file);
}
bool use_metainfo_from_file(tr_torrent_metainfo const* metainfo, char const* filename, tr_error* error);
// ---
void set_has_piece(tr_piece_index_t piece, bool has)
@ -1224,6 +1233,7 @@ private:
void on_metainfo_updated();
void on_metainfo_completed();
void on_have_all_metainfo();
void on_piece_completed(tr_piece_index_t piece);
void on_piece_failed(tr_piece_index_t piece);
void on_file_completed(tr_file_index_t file);
@ -1232,6 +1242,8 @@ private:
void create_empty_files() const;
void recheck_completeness();
[[nodiscard]] bool use_new_metainfo(tr_error* error);
void set_location_in_session_thread(std::string_view path, bool move_from_old_path, int volatile* setme_state);
void rename_path_in_session_thread(
@ -1261,6 +1273,11 @@ private:
tr_torrent_metainfo metainfo_;
/* Used when the torrent has been created with a magnet link
* and we're in the process of downloading the metainfo from
* other peers */
std::unique_ptr<tr_metadata_download> metadata_download_;
tr_bandwidth bandwidth_;
tr_completion completion_;

View File

@ -383,7 +383,7 @@ protected:
// 1048576 files-filled-with-zeroes/1048576
// 4096 files-filled-with-zeroes/4096
// 512 files-filled-with-zeroes/512
char const* benc_base64 =
static auto constexpr BencBase64 =
"ZDg6YW5ub3VuY2UzMTpodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Fubm91bmNlMTA6Y3JlYXRlZCBi"
"eTI1OlRyYW5zbWlzc2lvbi8yLjYxICgxMzQwNykxMzpjcmVhdGlvbiBkYXRlaTEzNTg3MDQwNzVl"
"ODplbmNvZGluZzU6VVRGLTg0OmluZm9kNTpmaWxlc2xkNjpsZW5ndGhpMTA0ODU3NmU0OnBhdGhs"
@ -404,7 +404,7 @@ protected:
"OnByaXZhdGVpMGVlZQ==";
// create the torrent ctor
auto const benc = tr_base64_decode(benc_base64);
auto const benc = tr_base64_decode(BencBase64);
EXPECT_LT(0U, std::size(benc));
auto* ctor = tr_ctorNew(session_);
auto error = tr_error{};
@ -448,6 +448,20 @@ protected:
return tor;
}
[[nodiscard]] tr_torrent* zeroTorrentMagnetInit()
{
static auto constexpr V1Hash = "fa5794674a18241bec985ddc3390e3cb171345e4";
auto ctor = tr_ctorNew(session_);
ctor->set_metainfo_from_magnet_link(V1Hash);
tr_ctorSetPaused(ctor, TR_FORCE, true);
auto* const tor = tr_torrentNew(ctor, nullptr);
EXPECT_NE(nullptr, tor);
tr_ctorFree(ctor);
return tor;
}
void blockingTorrentVerify(tr_torrent* tor)
{
EXPECT_NE(nullptr, tor->session);

View File

@ -4,6 +4,7 @@
// License text can be found in the licenses/ folder.
#include <cstddef> // size_t
#include <future>
#include <string>
#include <libtransmission/error.h>
@ -28,16 +29,16 @@ TEST_F(TorrentMagnetTest, getMetadataPiece)
};
auto piece = int{ 0 };
auto info_dict_size = size_t{ 0U };
auto data = tr_metadata_piece{};
for (;;)
{
if (!tr_torrentGetMetadataPiece(tor, piece++, data))
auto data = tor->get_metadata_piece(piece++);
if (!data)
{
break;
}
benc.append(reinterpret_cast<char const*>(std::data(data)), std::size(data));
info_dict_size += std::size(data);
benc.append(reinterpret_cast<char const*>(std::data(*data)), std::size(*data));
info_dict_size += std::size(*data);
}
benc.append("e");
EXPECT_EQ(tor->info_dict_size(), info_dict_size);
@ -50,4 +51,47 @@ TEST_F(TorrentMagnetTest, getMetadataPiece)
EXPECT_EQ(tor->piece_hash(0), torrent_metainfo.piece_hash(0));
}
TEST_F(TorrentMagnetTest, setMetadataPiece)
{
static auto constexpr InfoDictBase64 =
"ZDU6ZmlsZXNsZDY6bGVuZ3RoaTEwNDg1NzZlNDpwYXRobDc6MTA0ODU3NmVlZDY6bGVuZ3RoaTQw"
"OTZlNDpwYXRobDQ6NDA5NmVlZDY6bGVuZ3RoaTUxMmU0OnBhdGhsMzo1MTJlZWU0Om5hbWUyNDpm"
"aWxlcy1maWxsZWQtd2l0aC16ZXJvZXMxMjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXM2NjA6"
"UYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP"
"1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj"
"/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv17"
"26aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGEx"
"Uv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJ"
"tGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GI"
"QxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZC"
"S1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8K"
"T9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9um"
"o/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9"
"e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRh"
"MVL9e9umo/8KT9ZCSzpX+QPk899JzAVbjTNoaVd8IP9dNzpwcml2YXRlaTBlZQ==";
auto* const tor = zeroTorrentMagnetInit();
EXPECT_NE(nullptr, tor);
EXPECT_FALSE(tor->has_metainfo());
auto promise = std::promise<void>{};
auto future = promise.get_future();
session_->run_in_session_thread(
[tor, &promise]()
{
auto const metainfo_benc = tr_base64_decode(InfoDictBase64);
auto const metainfo_size = std::size(metainfo_benc);
EXPECT_LE(metainfo_size, MetadataPieceSize);
tor->maybe_start_metadata_transfer(metainfo_size);
tor->set_metadata_piece(0, std::data(metainfo_benc), metainfo_size);
EXPECT_TRUE(tor->has_metainfo());
EXPECT_EQ(tor->info_dict_size(), metainfo_size);
EXPECT_EQ(tor->get_metadata_percent(), 1.0);
promise.set_value();
});
future.wait();
}
} // namespace libtransmission::test