From 10d047005aabd8a96c7ff5d29ff76ca00a6ba8ff Mon Sep 17 00:00:00 2001 From: Yat Ho Date: Wed, 3 Jan 2024 11:04:17 +0800 Subject: [PATCH] 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 --- libtransmission/peer-mgr.cc | 1 - libtransmission/peer-msgs.cc | 59 ++-- libtransmission/torrent-magnet.cc | 282 +++++++++---------- libtransmission/torrent-magnet.h | 71 +++-- libtransmission/torrent.cc | 8 +- libtransmission/torrent.h | 29 +- tests/libtransmission/test-fixtures.h | 18 +- tests/libtransmission/torrent-magnet-test.cc | 52 +++- 8 files changed, 302 insertions(+), 218 deletions(-) diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 91728263f..d30727b0b 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -2256,7 +2256,6 @@ void tr_peerMgr::bandwidth_pulse() for (auto* const tor : torrents_) { tor->do_idle_work(); - tr_torrentMagnetDoIdleWork(tor); } reconnect_pulse(); diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index ffef9f641..46701b275 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -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(i); + msgs->peerSupportsPex = ut_pex != 0; + msgs->ut_pex_id = static_cast(ut_pex); logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_pex is {:d}"), static_cast(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(i); + msgs->peerSupportsMetadataXfer = ut_metadata != 0; + msgs->ut_metadata_id = static_cast(ut_metadata); logtrace(msgs, fmt::format(FMT_STRING("msgs->ut_metadata_id is {:d}"), static_cast(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) diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc index bcf768bb9..8732c0745 100644 --- a/libtransmission/torrent-magnet.cc +++ b/libtransmission/torrent-magnet.cc @@ -4,11 +4,9 @@ // License text can be found in the licenses/ folder. #include -#include /* INT_MAX */ #include #include #include -#include #include #include #include @@ -16,7 +14,6 @@ #include #include #include // std::move -#include #include @@ -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{}; - - 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(); - 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(name(), size); +} + +[[nodiscard]] std::optional 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(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(std::data(setme)), std::size(setme)); + auto const piece_len = static_cast(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(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(data), len, std::begin(m->metadata) + offset); + auto const offset = piece * MetadataPieceSize; + std::copy_n(reinterpret_cast(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 tr_torrentGetNextMetadataRequest(tr_torrent* tor, time_t now) +[[nodiscard]] std::optional 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 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 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(n); - } + return (n - std::size(pieces_needed_)) / static_cast(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(); diff --git a/libtransmission/torrent-magnet.h b/libtransmission/torrent-magnet.h index 58be0499f..72fabfb17 100644 --- a/libtransmission/torrent-magnet.h +++ b/libtransmission/torrent-magnet.h @@ -13,46 +13,67 @@ #include // int64_t #include // time_t #include +#include +#include #include +#include +#include #include #include -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; -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::max(); + } + + bool set_metadata_piece(int64_t piece, void const* data, size_t len); + + [[nodiscard]] std::optional 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 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 pieces_needed; + void create_all_needed(int64_t n_pieces) noexcept; - int piece_count = 0; + std::vector metadata_; + std::deque 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 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); diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 5078b1ade..3d6f87ca9 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -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(); diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 1a6bec144..d35273be1 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -148,7 +148,6 @@ struct tr_torrent final : public tr_completion::torrent_view uint64_t cur_ = {}; }; -public: using labels_t = std::vector; using VerifyDoneCallback = std::function; @@ -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 get_metadata_piece(int64_t piece) const; + + void set_metadata_piece(int64_t piece, void const* data, size_t len); + + [[nodiscard]] std::optional 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 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 metadata_download_; + tr_bandwidth bandwidth_; tr_completion completion_; diff --git a/tests/libtransmission/test-fixtures.h b/tests/libtransmission/test-fixtures.h index 62f8d0d30..e34671558 100644 --- a/tests/libtransmission/test-fixtures.h +++ b/tests/libtransmission/test-fixtures.h @@ -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); diff --git a/tests/libtransmission/torrent-magnet-test.cc b/tests/libtransmission/torrent-magnet-test.cc index ac20c173e..100ffa19b 100644 --- a/tests/libtransmission/torrent-magnet-test.cc +++ b/tests/libtransmission/torrent-magnet-test.cc @@ -4,6 +4,7 @@ // License text can be found in the licenses/ folder. #include // size_t +#include #include #include @@ -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(std::data(data)), std::size(data)); - info_dict_size += std::size(data); + benc.append(reinterpret_cast(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{}; + 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