From 0c576aa8eb05ac233c445f9db43907fa9eb78da4 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Aug 2026 11:11:11 -0400 Subject: [PATCH 01/38] increase announce interval if tracker responds with failure reason --- libi2pd_client/Torrents.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index d7f24531..4503106c 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -524,6 +524,10 @@ namespace torrents { auto [reason, l] = ExtractByteString (buf); LogPrint (eLogError, "Torrents: Tracker error: ", reason); + // double interval if tracker failure + int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackersInfo[trackerID])*2); + std::get<1>(m_TrackersInfo[trackerID]) = interval; + std::get<2>(m_TrackersInfo[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; return l; } return 0; @@ -1275,6 +1279,13 @@ namespace torrents size_t numPieces = m_Torrent->GetNumPieces (); m_RemoteBitfield.resize (numPieces); m_RemoteBitfield.set (); + if (!m_Torrent->IsComplete ()) + { + m_IsInterested = true; + SendInterestedMsg (); + } + else + Terminate (); // we don't need this connection } void PeerConnection::HandleHaveNoneMsg () @@ -1572,6 +1583,8 @@ namespace torrents return 0; }); } + else + LogPrint (eLogInfo, "Torrents: Unexecpetd extended message type ", (int)buf[0], " received"); } void PeerConnection::SendExtendedMsg () From 0b941d8f1078072b0affb89dc47bc84e57acf467 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Aug 2026 12:43:49 -0400 Subject: [PATCH 02/38] create remote bitfield on have message if bitfeild message was never received --- libi2pd_client/Torrents.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 4503106c..09f355f0 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -1201,6 +1201,8 @@ namespace torrents void PeerConnection::HandleHaveMsg (const uint8_t * buf, size_t len) { if (len < 4) return; + if (m_RemoteBitfield.empty ()) // bitfield was not received before because was empty + m_RemoteBitfield.resize (m_Torrent->GetNumPieces ()); uint32_t index = bufbe32toh (buf); if (index < m_RemoteBitfield.size ()) { From d2a8293971515b9ba3640c9924fd37f39af180f4 Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:00:49 +0300 Subject: [PATCH 03/38] hold a reference to the destination in UDP tunnels --- libi2pd_client/UDPTunnel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp index 6c509c08..0749b37e 100644 --- a/libi2pd_client/UDPTunnel.cpp +++ b/libi2pd_client/UDPTunnel.cpp @@ -470,11 +470,15 @@ namespace client m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress), m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip) { + // without a reference the destination counts as unused and a config reload + // stops it, leaving the tunnel with a destination that receives nothing + if (m_LocalDest) m_LocalDest->Acquire (); } I2PUDPServerTunnel::~I2PUDPServerTunnel () { Stop (); + if (m_LocalDest) m_LocalDest->Release (); } void I2PUDPServerTunnel::Start () @@ -594,11 +598,13 @@ namespace client m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort), m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip), m_DatagramVersion (datagramVersion) { + if (m_LocalDest) m_LocalDest->Acquire (); } I2PUDPClientTunnel::~I2PUDPClientTunnel () { Stop (); + if (m_LocalDest) m_LocalDest->Release (); } void I2PUDPClientTunnel::Start () From f58f9e2838827c4f45e7fa2cbff8028011fdb33b Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Aug 2026 13:24:40 -0400 Subject: [PATCH 04/38] correct signature for datagram2 --- libi2pd/Datagram.cpp | 8 ++++---- libi2pd/Datagram.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 9e3362a2..dbc950f2 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -98,10 +98,10 @@ namespace datagram { constexpr uint8_t flags[] = { 0x00, 0x02 }; // datagram2, no options // signature - std::vector signedData (len + m_From.size () + 2); - memcpy (signedData.data (), m_From.data (), m_From.size ()); - memcpy (signedData.data () + m_From.size (), flags, 2); - memcpy (signedData.data () + m_From.size () + 2, payload, len); + std::vector signedData (len + 32 + 2); + memcpy (signedData.data (), session->GetRemoteIdent (), 32); + memcpy (signedData.data () + 32, flags, 2); + memcpy (signedData.data () + 32 + 2, payload, len); m_Owner->Sign (signedData.data (), signedData.size (), m_Signature.data ()); // TODO: offline signatures and options msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {flags, 2}, {payload, len}, diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h index 22011153..478ec51d 100644 --- a/libi2pd/Datagram.h +++ b/libi2pd/Datagram.h @@ -80,6 +80,7 @@ namespace datagram bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); } void SetRemoteLeaseSet (std::shared_ptr ls) { m_RemoteLeaseSet = ls; } std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; } + const i2p::data::IdentHash& GetRemoteIdent () const { return m_RemoteIdent; } DatagramVersion GetVersion () const { return m_Version; } void SetVersion (DatagramVersion version) { m_Version = version; } From d7080fecb982d91af18fb131229cf6b625eb85a2 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Aug 2026 19:01:22 -0400 Subject: [PATCH 05/38] maintain connection to datagram tracker --- libi2pd_client/Torrents.cpp | 42 +++++------ libi2pd_client/Torrents.h | 18 ++--- libi2pd_client/TorrentsRPC.cpp | 12 ++- libi2pd_client/TorrentsTunnel.cpp | 118 +++++++++++++++++++----------- libi2pd_client/TorrentsTunnel.h | 25 ++++--- 5 files changed, 123 insertions(+), 92 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 09f355f0..1dba96b6 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -490,8 +490,8 @@ namespace torrents void Torrent::ParseTrackerResponse (size_t trackerID, std::string_view buf) { - if (trackerID >= m_TrackersInfo.size ()) - m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL, + if (trackerID >= m_TrackerStats.size ()) + m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, 0, 0, 0, i2p::util::GetSecondsSinceEpoch ()}); ParseDictionary (buf, [this, trackerID](std::string_view key, std::string_view buf)->size_t { @@ -501,8 +501,8 @@ namespace torrents if (l) { int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, (int)value*1000); // in milliseconds - std::get<1>(m_TrackersInfo[trackerID]) = interval; - std::get<2>(m_TrackersInfo[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; // reset next request + std::get<1>(m_TrackerStats[trackerID]) = interval; + std::get<2>(m_TrackerStats[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; // reset next request } return l; } @@ -511,13 +511,13 @@ namespace torrents else if (key == "complete") { auto [seeders, l] = ExtractInteger (buf); - if (l) std::get<3>(m_TrackersInfo[trackerID]) = seeders; + if (l) std::get<3>(m_TrackerStats[trackerID]) = seeders; return l; } else if (key == "incomplete") { auto [leechers, l] = ExtractInteger (buf); - if (l) std::get<4>(m_TrackersInfo[trackerID]) = leechers; + if (l) std::get<4>(m_TrackerStats[trackerID]) = leechers; return l; } else if (key == "failure reason") @@ -525,19 +525,19 @@ namespace torrents auto [reason, l] = ExtractByteString (buf); LogPrint (eLogError, "Torrents: Tracker error: ", reason); // double interval if tracker failure - int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackersInfo[trackerID])*2); - std::get<1>(m_TrackersInfo[trackerID]) = interval; - std::get<2>(m_TrackersInfo[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; + int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackerStats[trackerID])*2); + std::get<1>(m_TrackerStats[trackerID]) = interval; + std::get<2>(m_TrackerStats[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; return l; } return 0; }); - std::get<5>(m_TrackersInfo[trackerID]) = i2p::util::GetSecondsSinceEpoch (); + std::get<5>(m_TrackerStats[trackerID]) = i2p::util::GetSecondsSinceEpoch (); } size_t Torrent::ParsePeers (size_t trackerID, std::string_view buf) { - auto& peers = std::get<0>(m_TrackersInfo[trackerID]); + auto& peers = std::get<0>(m_TrackerStats[trackerID]); peers.clear (); auto [hashes, len] = ExtractByteString (buf); while (!hashes.empty ()) @@ -551,10 +551,10 @@ namespace torrents void Torrent::HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval, const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers) { - if (trackerID >= m_TrackersInfo.size ()) - m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL, + if (trackerID >= m_TrackerStats.size ()) + m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, 0, 0, 0, i2p::util::GetSecondsSinceEpoch () }); - auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, lastUpdateTime] = m_TrackersInfo[trackerID]; + auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, lastUpdateTime] = m_TrackerStats[trackerID]; trackerRequestInterval = interval*1000; // milliseconds nextRequestTime = i2p::util::GetMonotonicMilliseconds () + trackerRequestInterval; seeders = numSeeders; @@ -795,10 +795,10 @@ namespace torrents std::unordered_set Torrent::GetPeers () const { - if (m_TrackersInfo.size () == 1) - return std::get<0>(m_TrackersInfo.front ()); + if (m_TrackerStats.size () == 1) + return std::get<0>(m_TrackerStats.front ()); std::unordered_set ret; - for (const auto& it: m_TrackersInfo) + for (const auto& it: m_TrackerStats) { const auto& peers = std::get<0>(it); ret.insert (peers.begin (), peers.end ()); @@ -808,16 +808,16 @@ namespace torrents uint64_t Torrent::GetNextTrackerRequestTime (size_t trackerID) const { - if (trackerID < m_TrackersInfo.size ()) return std::get<2>(m_TrackersInfo[trackerID]); + if (trackerID < m_TrackerStats.size ()) return std::get<2>(m_TrackerStats[trackerID]); return 0; } void Torrent::SetNextTrackerRequestTime (size_t trackerID, uint64_t ts) { - if (trackerID >= m_TrackersInfo.size ()) - m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL, + if (trackerID >= m_TrackerStats.size ()) + m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, 0, 0, 0, i2p::util::GetSecondsSinceEpoch ()}); - std::get<2>(m_TrackersInfo[trackerID]) = ts; + std::get<2>(m_TrackerStats[trackerID]) = ts; } TorrentStatus Torrent::GetStatus () const diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 38095366..9ccabdbb 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -146,12 +146,12 @@ namespace torrents using RequestedBlock = std::tuple; // (index, offset, len) class Torrent final { + using TrackerStats = std::tuple, int, uint64_t, int, int, uint64_t>; + // (peers, interval, next tracker request time in monotonic milliseconds, + // seeders, leechers, last update time in seconds since epoch) public: using InfoHash = std::array; - using TrackerInfo = std::tuple, int, uint64_t, int, int, uint64_t>; - // (peers, interval, next tracker request time in monotonic milliseconds, - // seeders, leechers, last update time in seconds since epoch) Torrent (std::string_view buf); void ParseTrackerResponse (size_t trackerID, std::string_view buf); @@ -173,7 +173,7 @@ namespace torrents std::list >& GetFiles () { return m_Files; } size_t GetLength () const { return m_Length; } size_t GetPieceLength () const { return m_PieceLength; } - int GetInterval (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<1>(m_TrackersInfo[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; } + int GetInterval (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<1>(m_TrackerStats[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; } const InfoHash& GetInfoHash () const { return m_InfoHash; } size_t GetLeft () const; std::string GetHexStringInfoHash () const; // in url format @@ -210,10 +210,10 @@ namespace torrents int GetNumUploadingToPeers () const { return m_NumUploadingToPeers; } void SetNumUploadingToPeers (int numUploadingToPeers) { m_NumUploadingToPeers = numUploadingToPeers; } - int GetNumSeeders (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<3>(m_TrackersInfo[trackerID]) : 0; } - int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<4>(m_TrackersInfo[trackerID]) : 0; } - int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<0>(m_TrackersInfo[trackerID]).size () : 0; } - uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<5>(m_TrackersInfo[trackerID]) : 0; } + int GetNumSeeders (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<3>(m_TrackerStats[trackerID]) : 0; } + int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<4>(m_TrackerStats[trackerID]) : 0; } + int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<0>(m_TrackerStats[trackerID]).size () : 0; } + uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<5>(m_TrackerStats[trackerID]) : 0; } private: @@ -230,7 +230,7 @@ namespace torrents size_t m_Length, m_PieceLength; InfoHash m_InfoHash; // SHA1 std::vector m_Pieces; - std::vector m_TrackersInfo; + std::vector m_TrackerStats; bool m_IsComplete, m_IsStopped; std::list > m_Files; // list of (path, length) size_t m_Uploaded, m_Downloaded; diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index f85bfceb..873c8a96 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -411,12 +411,11 @@ namespace torrents boost::json::array JSONRPCHandler::GetTrackers (std::shared_ptr torrent) const { boost::json::array trackers; - const auto& tunnelTrackers = m_Tunnel->GetTrackers (); - for (size_t i = 0; i < tunnelTrackers.size (); i++) + for (size_t i = 0; i < m_Tunnel->GetNumTrackers (); i++) { boost::json::object tracker; tracker["id"] = i; - tracker["announce"] = tunnelTrackers[i]; + tracker["announce"] = m_Tunnel->GetTrackerAnnounce (i); tracker["tier"] = 0; trackers.push_back (tracker); } @@ -426,14 +425,13 @@ namespace torrents boost::json::array JSONRPCHandler::GetTrackerStats (std::shared_ptr torrent) const { boost::json::array trackers; - const auto& tunnelTrackers = m_Tunnel->GetTrackers (); - for (size_t i = 0; i < tunnelTrackers.size (); i++) + for (size_t i = 0; i < m_Tunnel->GetNumTrackers (); i++) { boost::json::object tracker; tracker["id"] = i; - tracker["announce"] = tunnelTrackers[i]; + tracker["announce"] = m_Tunnel->GetTrackerAnnounce (i); i2p::http::URL announceURL; - announceURL.parse (tunnelTrackers[i]); + announceURL.parse (m_Tunnel->GetTrackerAnnounce (i)); tracker["host"]= announceURL.host; tracker["announceState"] = 1; tracker["scrapeState"] = 0; diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 724f7c07..8ac78ca1 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -33,7 +33,12 @@ namespace torrents m_PeerID += localDestination->GetIdentHash ().ToBase64 (); m_PeerID.resize (20, '0'); if (!trackers.empty ()) - boost::split(m_Trackers, trackers, boost::is_any_of(","), boost::token_compress_on); + { + std::vector trackersList; + boost::split(trackersList, trackers, boost::is_any_of(","), boost::token_compress_on); + for (const auto& it: trackersList) + m_Trackers.emplace_back (TrackerInfo{ it, 0, 0, 0 }); + } } void TorrentsTunnel::Start () @@ -473,7 +478,7 @@ namespace torrents if (!torrent) return; i2p::http::URL reqURL; if (trackerID < m_Trackers.size()) - reqURL.parse (m_Trackers[trackerID]); + reqURL.parse (std::get<0>(m_Trackers[trackerID])); else reqURL.parse (torrent->GetAnnounce ()); #if __cplusplus >= 202002L // C++20 @@ -487,7 +492,19 @@ namespace torrents } if (reqURL.schema == "udp") { - ConnectToDatagramTracker (torrent, trackerID, reqURL.host, reqURL.port, event); + if (trackerID >= m_Trackers.size()) return; + auto ts = i2p::util::GetMonotonicMilliseconds (); + auto& [announce, connectionID, expiration, fromPort] = m_Trackers[trackerID]; + if (connectionID && ts <= expiration) + SendAnnounceToDatagramTracker (trackerID, connectionID, torrent, reqURL.host, reqURL.port, fromPort, event); + else + { + if (!expiration || ts >= expiration) // don't connect if pending connection + { + expiration = ts + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT; + ConnectToDatagramTracker (trackerID, reqURL.host, reqURL.port); + } + } return; } std::map params; @@ -604,7 +621,7 @@ namespace torrents auto it = m_DatragramTrackerTransactions.begin (); while (it != m_DatragramTrackerTransactions.end ()) { - if (ts > (std::get<5>(it->second) + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT)*1000LL) + if (ts > std::get<3>(it->second) + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT) it = m_DatragramTrackerTransactions.erase (it); else it++; @@ -820,8 +837,7 @@ namespace torrents LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker error response: ", std::string_view ((const char *)(buf + 4), len - 4)); } - void TorrentsTunnel::ConnectToDatagramTracker (std::shared_ptr torrent, - size_t trackerID, std::string_view dest, uint16_t port, TrackerAnnounceEvent event) + void TorrentsTunnel::ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port) { LogPrint (eLogDebug, "TorrentsTunnel: Connecting to datagram tracker ", dest, ":", port); auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); @@ -840,8 +856,8 @@ namespace torrents auto session = dgramDest->GetSession (address->identHash); if (session) { - m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (torrent, - trackerID, address->identHash, port, fromPort, i2p::util::GetMonotonicSeconds (), event)); + m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple ( + trackerID, fromPort, std::shared_ptr(nullptr), i2p::util::GetMonotonicMilliseconds ())); session->SetVersion (i2p::datagram::eDatagramV2); // send datagram2 dgramDest->SendDatagram (session, connectRequest, 16, fromPort, port); } @@ -871,48 +887,62 @@ namespace torrents return; } uint64_t connectionID = bufbe64toh (buf + 4); - auto [torrent, trackerID, ident, port, fromPort, ts, event] = it->second; - if (!torrent.expired ()) - SendAnnounceToDatagramTracker (transactionID, connectionID, torrent.lock (), ident, port, fromPort, event); - else - m_DatragramTrackerTransactions.erase (it); + auto [trackerID, fromPort, torrent, ts] = it->second; + if (trackerID < m_Trackers.size ()) + { + auto& [announce, connID, connExpiration, connFromPort] = m_Trackers[trackerID]; + connID = connectionID; + connExpiration = i2p::util::GetMonotonicMilliseconds () + DATAGRAM_TRACKER_CONNECTION_EXPIRATION; + connFromPort = fromPort; + } + m_DatragramTrackerTransactions.erase (it); } - void TorrentsTunnel::SendAnnounceToDatagramTracker (uint32_t transactionID, - uint64_t connectionID, std::shared_ptr torrent, const i2p::data::IdentHash& ident, - uint16_t port, uint16_t fromPort, TrackerAnnounceEvent event) + void TorrentsTunnel::SendAnnounceToDatagramTracker (size_t trackerID, uint64_t connectionID, + std::shared_ptr torrent, std::string_view dest, uint16_t port, + uint16_t fromPort, TrackerAnnounceEvent event) { - auto localDestination = GetLocalDestination (); - auto dgramDest = localDestination->GetDatagramDestination (); - if (dgramDest) + LogPrint (eLogDebug, "TorrentsTunnel: Sending announce to datagram tracker ", dest, ":", port); + auto address = i2p::client::context.GetAddressBook ().GetAddress (dest); + if (address && address->IsIdentHash ()) { - uint8_t announce[98]; - htobe64buf (announce, connectionID); // connection_id - htobe32buf (announce + 8, eDatagramTrackerActionAnnounce); // action - htobe32buf (announce + 12, transactionID); // transaction_id - memcpy (announce + 16, torrent->GetInfoHash ().data (), 20); // info_hash - memcpy (announce + 36, m_PeerID.data (), 20); // peer_id - auto left = torrent->GetLeft (); - htobe64buf (announce + 56, torrent->GetLength () - left); // downloaded - htobe64buf (announce + 64, left); // left - htobe64buf (announce + 72, torrent->GetUploaded ()); // uploaded - htobe32buf (announce + 80, event); // event - htobe32buf (announce + 84, 0); // IP address 0:not used - htobe32buf (announce + 88, 0); // key, ignored - int numWant = 0; - if (!torrent->IsComplete () && (event == eTrackerAnnounceEventNone || event == eTrackerAnnounceEventStarted)) - numWant = TRACKER_MAX_NUM_WANT; - htobe32buf (announce + 92, numWant); // num_want - htobe16buf (announce + 96, fromPort); // from port - auto session = dgramDest->GetSession (ident); - if (session) + auto localDestination = GetLocalDestination (); + auto dgramDest = localDestination->GetDatagramDestination (); + if (dgramDest) { - session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3 - dgramDest->SendDatagram (session, announce, 98, fromPort, port); + uint8_t announce[98]; + htobe64buf (announce, connectionID); // connection_id + htobe32buf (announce + 8, eDatagramTrackerActionAnnounce); // action + uint32_t transactionID = localDestination->GetRng()(); + htobe32buf (announce + 12, transactionID); // transaction_id + memcpy (announce + 16, torrent->GetInfoHash ().data (), 20); // info_hash + memcpy (announce + 36, m_PeerID.data (), 20); // peer_id + auto left = torrent->GetLeft (); + htobe64buf (announce + 56, torrent->GetLength () - left); // downloaded + htobe64buf (announce + 64, left); // left + htobe64buf (announce + 72, torrent->GetUploaded ()); // uploaded + htobe32buf (announce + 80, event); // event + htobe32buf (announce + 84, 0); // IP address 0:not used + htobe32buf (announce + 88, 0); // key, ignored + int numWant = 0; + if (!torrent->IsComplete () && (event == eTrackerAnnounceEventNone || event == eTrackerAnnounceEventStarted)) + numWant = TRACKER_MAX_NUM_WANT; + htobe32buf (announce + 92, numWant); // num_want + htobe16buf (announce + 96, fromPort); // from port + auto session = dgramDest->GetSession (address->identHash); + if (session) + { + m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple ( + trackerID, fromPort, torrent, i2p::util::GetMonotonicMilliseconds ())); + session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3 + dgramDest->SendDatagram (session, announce, 98, fromPort, port); + } + else + LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session"); } - else - LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session"); } + else + LogPrint (eLogInfo, "TorrentsTunnel: Tracker not found: ", dest); } void TorrentsTunnel::HandleAnnounceResponse (const uint8_t * buf, size_t len) @@ -930,7 +960,7 @@ namespace torrents LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker transaction ", transactionID, " not found"); return; } - auto [torrent, trackerID, ident, port, fromPort, ts, event] = it->second; + auto [trackerID, fromPort, torrent, ts] = it->second; if (!torrent.expired ()) { uint32_t interval = bufbe32toh (buf + 4); diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index 80f7b915..af7cc759 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -32,7 +32,8 @@ namespace i2p namespace torrents { constexpr int TRACKER_RESPONSE_TIMEOUT = 8; // in seconds - constexpr int DATAGRAM_TRACKER_TRANSACTION_TIMEOUT = 60; // in seconds + constexpr int DATAGRAM_TRACKER_TRANSACTION_TIMEOUT = 10000; // in milliseconds + constexpr int DATAGRAM_TRACKER_CONNECTION_EXPIRATION = 60000; // in milliseconds constexpr int TRACKER_REQUESTS_CHECK_TIMEOUT = 1900; // in milliseconds constexpr int RECONNECT_CHECK_INTERVAL = 70; // in seconds constexpr int TRACKER_REQUESTS_INTERVAL_VARIANCE = 3000; // in milliseconds @@ -76,6 +77,9 @@ namespace torrents void Stop () { StopWorkAndFinishTasks (); } }; + using TrackerInfo = std::tuple; + // (announce, connection_id, connection expiration time in monotonic milliseconds, connction from_port) + public: TorrentsTunnel (std::string_view name, std::shared_ptr localDestination, @@ -86,7 +90,8 @@ namespace torrents auto& GetDiskIOService () { return m_DiskIOService.GetService (); }; const std::string& GetPeerID () const { return m_PeerID; } - const std::vector& GetTrackers () const { return m_Trackers; } + std::string_view GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : 0; } + size_t GetNumTrackers () const { return m_Trackers.size (); } std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const; std::shared_ptr FindTorrentByID (int id) const; std::vector GetTorrentIDs () const; @@ -134,29 +139,27 @@ namespace torrents void UpdateStats (); void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); - void ConnectToDatagramTracker (std::shared_ptr torrent, size_t trackerID, - std::string_view dest, uint16_t port, TrackerAnnounceEvent event); + void ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port); void HandleConnectResponse (const uint8_t * buf, size_t len); void HandleErrorResponse (const uint8_t * buf, size_t len); void HandleAnnounceResponse (const uint8_t * buf, size_t len); - void SendAnnounceToDatagramTracker (uint32_t transactionID, uint64_t connectionID, - std::shared_ptr torrent, const i2p::data::IdentHash& ident, - uint16_t port, uint16_t fromPort, TrackerAnnounceEvent event); + void SendAnnounceToDatagramTracker (size_t trackerID, uint64_t connectionID, + std::shared_ptr torrent, std::string_view dest, uint16_t port, + uint16_t fromPort, TrackerAnnounceEvent event); private: std::string m_Name, m_PeerID; // 20 characters std::filesystem::path m_TorrentsDir; - std::vector m_Trackers; + std::vector m_Trackers; std::map > m_Torrents; std::map > m_TorrentsByID; mutable std::mutex m_TorrentsMutex; boost::asio::steady_timer m_TrackerRequestsCheckTimer, m_KeepAliveCheckTimer, m_ReconnectCheckTimer, m_TorrentsStatusUpdateTimer; DiskIOService m_DiskIOService; - std::unordered_map, size_t, i2p::data::IdentHash, - uint16_t, uint16_t, uint64_t, TrackerAnnounceEvent> > m_DatragramTrackerTransactions; - // transactionID->(torrent, trackerID, ident, port, fromPort, ts in monotonic seconds, event) + std::unordered_map, uint64_t > > m_DatragramTrackerTransactions; + // transactionID->(trackerID, from_port, torrent, timestamp monotonic milliseconds) }; } From 8dad408660e26f37b8c5f11f66d53ee1e51c8e97 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Aug 2026 19:18:03 -0400 Subject: [PATCH 06/38] fixed typo --- libi2pd_client/TorrentsTunnel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index af7cc759..2b12630c 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -90,7 +90,7 @@ namespace torrents auto& GetDiskIOService () { return m_DiskIOService.GetService (); }; const std::string& GetPeerID () const { return m_PeerID; } - std::string_view GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : 0; } + std::string_view GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : ""; } size_t GetNumTrackers () const { return m_Trackers.size (); } std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const; std::shared_ptr FindTorrentByID (int id) const; From 9ef68e2d93307ea5e38c29b3d61eba3b7002da37 Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Mon, 31 Aug 2026 02:33:05 +0300 Subject: [PATCH 07/38] stop parsing a truncated zip in reseed instead of looping forever --- libi2pd/Reseed.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index ee02a953..ca679911 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -330,7 +330,9 @@ namespace data { int numFiles = 0; size_t contentPos = s.tellg (); - while (!s.eof ()) + // not !eof: seekg clears eofbit, so a stream broken in the middle of a + // header stays not-eof forever and the loop never ends + while (s.good ()) { uint32_t signature; s.read ((char *)&signature, 4); @@ -357,13 +359,18 @@ namespace data uint16_t fileNameLength, extraFieldLength; s.read ((char *)&fileNameLength, 2); fileNameLength = le16toh (fileNameLength); + s.read ((char *)&extraFieldLength, 2); + extraFieldLength = le16toh (extraFieldLength); + if (!s.good ()) + { + LogPrint (eLogError, "Reseed: Truncated zip local file header"); + return numFiles; + } if ( fileNameLength >= 255 ) { // too big LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength); return numFiles; } - s.read ((char *)&extraFieldLength, 2); - extraFieldLength = le16toh (extraFieldLength); char localFileName[255]; s.read (localFileName, fileNameLength); localFileName[fileNameLength] = 0; From 7beb115d1ed3211a21dc9b5e04960923c7b8968c Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Mon, 31 Aug 2026 02:52:36 +0300 Subject: [PATCH 08/38] stop searching for a zip data descriptor on a broken stream --- libi2pd/Reseed.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index ca679911..4188f1c2 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -409,6 +409,12 @@ namespace data uint8_t * compressed = new uint8_t[compressedSize]; s.read ((char *)compressed, compressedSize); + if (!s.good ()) + { + LogPrint (eLogError, "Reseed: Truncated zip file data"); + delete[] compressed; + return numFiles; + } if (compressionMethod) // we assume Deflate { z_stream inflator; @@ -490,7 +496,7 @@ namespace data bool Reseeder::FindZipDataDescriptor (std::istream& s) { size_t nextInd = 0; - while (!s.eof ()) + while (s.good ()) // a broken stream never reaches eof, see ProcessZIPStream { uint8_t nextByte; s.read ((char *)&nextByte, 1); From 954e98eb4d9b62fd1daa9bfa5af310b80263504f Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Mon, 31 Aug 2026 03:00:47 +0300 Subject: [PATCH 09/38] fail on a truncated chunk instead of returning uninitialized bytes --- libi2pd/HTTP.cpp | 5 +++++ tests/test-http-merge_chunked.cpp | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index 03321cf5..f5c0b213 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -616,6 +616,11 @@ namespace http return false; /* too large chunk */ char * buf = new char[len]; in.read (buf, len); + if (in.gcount () != len) /* chunk is shorter than announced */ + { + delete[] buf; + return false; + } out.write (buf, len); delete[] buf; std::getline (in, hexLen); // read \r\n after chunk diff --git a/tests/test-http-merge_chunked.cpp b/tests/test-http-merge_chunked.cpp index 31b6a298..bdba5c3b 100644 --- a/tests/test-http-merge_chunked.cpp +++ b/tests/test-http-merge_chunked.cpp @@ -21,5 +21,11 @@ int main() { assert(MergeChunkedResponse(in, out) == true); assert(out.str() == "HTTP response with \r\nchunks."); + /* a chunk that promises more bytes than the response carries */ + std::stringstream truncated("20\r\nshort\r\n"); + std::stringstream out2; + + assert(MergeChunkedResponse(truncated, out2) == false); + return 0; } From 039d3feea718974fc6dd0d690c0a2c6d8071b4ae Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Mon, 31 Aug 2026 03:12:39 +0300 Subject: [PATCH 10/38] return false instead of throwing on a url with nothing after the schema --- libi2pd/HTTP.cpp | 1 + tests/test-http-url.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index f5c0b213..43a110bc 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -133,6 +133,7 @@ namespace http } /* hostname[:port][/path] */ + if (pos_p >= url.length ()) return false; /* nothing left after schema or user part */ if (url.at(pos_p) == '[') // ipv6 { auto pos_b = url.find(']', pos_p); diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index a5021c43..061c83ce 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -125,6 +125,12 @@ int main() { assert(url->frag == "frag"); delete url; + /* nothing after the schema or the user part: must not throw */ + url = new URL; + assert(url->parse("http://") == false); + assert(url->parse("http://user@") == false); + delete url; + return 0; } From e3d3d7f94657336e09cb161f1ee3e645aaaeab6c Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 30 Aug 2026 20:57:15 -0400 Subject: [PATCH 11/38] handle lifetime field from datagram tracker connect response --- libi2pd_client/TorrentsTunnel.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 8ac78ca1..3b933e45 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -887,12 +887,18 @@ namespace torrents return; } uint64_t connectionID = bufbe64toh (buf + 4); + int lifetime = DATAGRAM_TRACKER_CONNECTION_EXPIRATION; + if (len >= 14) + { + lifetime = std::max (bufbe16toh (buf + 12)*1000, DATAGRAM_TRACKER_CONNECTION_EXPIRATION); + LogPrint (eLogDebug, "TorrentsTunnel: Datagram tracker connection lifetime set to ", lifetime/1000, " seconds"); + } auto [trackerID, fromPort, torrent, ts] = it->second; if (trackerID < m_Trackers.size ()) { auto& [announce, connID, connExpiration, connFromPort] = m_Trackers[trackerID]; connID = connectionID; - connExpiration = i2p::util::GetMonotonicMilliseconds () + DATAGRAM_TRACKER_CONNECTION_EXPIRATION; + connExpiration = i2p::util::GetMonotonicMilliseconds () + lifetime; connFromPort = fromPort; } m_DatragramTrackerTransactions.erase (it); From 4fdb8c7c83f302f077d6d0fcf1b679f84e5c2e0c Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Mon, 31 Aug 2026 04:28:37 +0300 Subject: [PATCH 12/38] drop a tunnel message shorter than an I2NP header --- libi2pd/TunnelEndpoint.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp index c8b31343..8bdb90ba 100644 --- a/libi2pd/TunnelEndpoint.cpp +++ b/libi2pd/TunnelEndpoint.cpp @@ -322,6 +322,12 @@ namespace tunnel LogPrint (eLogInfo, "TunnelMessage: Message expired"); return; } + if (msg.data->GetLength () < I2NP_HEADER_SIZE) + { + // GetPayloadLength subtracts the header size, a shorter message wraps it around + LogPrint (eLogError, "TunnelMessage: Message of ", msg.data->GetLength (), " bytes is shorter than I2NP header, dropped"); + return; + } uint8_t typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: Handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); From 719812c92652e6b49f624e3d1d27d7a33bafbfc2 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 08:26:45 -0400 Subject: [PATCH 13/38] reset connection_id if expired --- libi2pd_client/TorrentsTunnel.cpp | 1 + libi2pd_client/TorrentsTunnel.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 3b933e45..682819b3 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -501,6 +501,7 @@ namespace torrents { if (!expiration || ts >= expiration) // don't connect if pending connection { + connectionID = 0; expiration = ts + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT; ConnectToDatagramTracker (trackerID, reqURL.host, reqURL.port); } diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index 2b12630c..a7dcaa49 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -90,7 +90,7 @@ namespace torrents auto& GetDiskIOService () { return m_DiskIOService.GetService (); }; const std::string& GetPeerID () const { return m_PeerID; } - std::string_view GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : ""; } + std::string GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : ""; } size_t GetNumTrackers () const { return m_Trackers.size (); } std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const; std::shared_ptr FindTorrentByID (int id) const; From aafaed473415cd57de5351be62163d9770cc3fc0 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 12:48:02 -0400 Subject: [PATCH 14/38] store and show tracker error --- libi2pd_client/Torrents.cpp | 28 ++++++++++++++++++++-------- libi2pd_client/Torrents.h | 8 ++++++-- libi2pd_client/TorrentsRPC.cpp | 13 +++++++++++-- libi2pd_client/TorrentsTunnel.cpp | 14 ++++++++++++-- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 1dba96b6..3a288b69 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -490,9 +490,8 @@ namespace torrents void Torrent::ParseTrackerResponse (size_t trackerID, std::string_view buf) { - if (trackerID >= m_TrackerStats.size ()) - m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, - 0, 0, 0, i2p::util::GetSecondsSinceEpoch ()}); + CheckTrackerStatsSize (trackerID); + std::get<6>(m_TrackerStats[trackerID]) = ""; // clear error ParseDictionary (buf, [this, trackerID](std::string_view key, std::string_view buf)->size_t { if (key == "interval") @@ -524,6 +523,7 @@ namespace torrents { auto [reason, l] = ExtractByteString (buf); LogPrint (eLogError, "Torrents: Tracker error: ", reason); + std::get<6>(m_TrackerStats[trackerID]) = reason; // double interval if tracker failure int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackerStats[trackerID])*2); std::get<1>(m_TrackerStats[trackerID]) = interval; @@ -551,10 +551,9 @@ namespace torrents void Torrent::HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval, const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers) { - if (trackerID >= m_TrackerStats.size ()) - m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, - 0, 0, 0, i2p::util::GetSecondsSinceEpoch () }); - auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, lastUpdateTime] = m_TrackerStats[trackerID]; + CheckTrackerStatsSize (trackerID); + auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, + lastUpdateTime, error] = m_TrackerStats[trackerID]; trackerRequestInterval = interval*1000; // milliseconds nextRequestTime = i2p::util::GetMonotonicMilliseconds () + trackerRequestInterval; seeders = numSeeders; @@ -571,6 +570,19 @@ namespace torrents } } + void Torrent::SetTrackerError (size_t trackerID, std::string_view error) + { + CheckTrackerStatsSize (trackerID); + std::get<6>(m_TrackerStats[trackerID]) = error; + } + + void Torrent::CheckTrackerStatsSize (size_t trackerID) + { + if (trackerID >= m_TrackerStats.size ()) + m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, + 0, 0, 0, i2p::util::GetSecondsSinceEpoch (), ""}); + } + std::pair, bool> Torrent::CreateBitfield () const { size_t numPieces = m_Pieces.size (); @@ -816,7 +828,7 @@ namespace torrents { if (trackerID >= m_TrackerStats.size ()) m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL, - 0, 0, 0, i2p::util::GetSecondsSinceEpoch ()}); + 0, 0, 0, i2p::util::GetSecondsSinceEpoch (), ""}); std::get<2>(m_TrackerStats[trackerID]) = ts; } diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 9ccabdbb..a8d3cc75 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -146,9 +146,10 @@ namespace torrents using RequestedBlock = std::tuple; // (index, offset, len) class Torrent final { - using TrackerStats = std::tuple, int, uint64_t, int, int, uint64_t>; + using TrackerStats = std::tuple, + int, uint64_t, int, int, uint64_t, std::string>; // (peers, interval, next tracker request time in monotonic milliseconds, - // seeders, leechers, last update time in seconds since epoch) + // seeders, leechers, last update time in seconds since epoch, error) public: using InfoHash = std::array; @@ -214,6 +215,8 @@ namespace torrents int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<4>(m_TrackerStats[trackerID]) : 0; } int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<0>(m_TrackerStats[trackerID]).size () : 0; } uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<5>(m_TrackerStats[trackerID]) : 0; } + std::string GetTrackerError (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<6>(m_TrackerStats[trackerID]) : ""; } + void SetTrackerError (size_t trackerID, std::string_view error); private: @@ -222,6 +225,7 @@ namespace torrents size_t ParsePeers (size_t trackerID, std::string_view buf); size_t ParseFiles (std::string_view buf); static bool IsSafeName (std::string_view name); // a name from a torrent file before it becomes a path + void CheckTrackerStatsSize (size_t trackerID); private: diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index 873c8a96..ca4b5158 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -443,8 +443,17 @@ namespace torrents tracker["seederCount"] = torrent->GetNumSeeders (i); tracker["leecherCount"] = torrent->GetNumLeechers (i); tracker["lastAnnouncePeerCount"] = torrent->GetNumPeers (i); - tracker["lastAnnounceResult"] = "Success"; // TODO: - tracker["lastAnnounceSucceeded"] = true; // TODO: + auto trackerError = torrent->GetTrackerError (i); + if (trackerError.empty ()) + { + tracker["lastAnnounceResult"] = "Success"; + tracker["lastAnnounceSucceeded"] = true; + } + else + { + tracker["lastAnnounceResult"] = trackerError; + tracker["lastAnnounceSucceeded"] = false; + } tracker["lastAnnounceTimedOut"] = false; // TODO: tracker["nextAnnounceTime"] = (torrent->GetNextTrackerRequestTime (i) - i2p::util::GetMonotonicMilliseconds () + i2p::util::GetMillisecondsSinceEpoch ())/1000; diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 682819b3..72ad7cb7 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -834,8 +834,18 @@ namespace torrents { uint32_t transactionID = bufbe32toh (buf); LogPrint (eLogDebug, "TorrentsTunnel: Datagram tracker action error response ", transactionID); - m_DatragramTrackerTransactions.erase (transactionID); - LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker error response: ", std::string_view ((const char *)(buf + 4), len - 4)); + auto it = m_DatragramTrackerTransactions.find (transactionID); + if (it == m_DatragramTrackerTransactions.end ()) + { + LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker transaction ", transactionID, " not found"); + return; + } + std::string_view error ((const char *)(buf + 4), len - 4); + LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker error response: ", error); + auto [trackerID, fromPort, torrent, ts] = it->second; + if (!torrent.expired ()) // response to announce + torrent.lock ()->SetTrackerError (trackerID, error); + m_DatragramTrackerTransactions.erase (it); } void TorrentsTunnel::ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port) From 184dfb1df93b0ec58d4dd75c559a6020cb6e319c Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 16:04:06 -0400 Subject: [PATCH 15/38] handle extended BEP10 messages --- libi2pd_client/Torrents.cpp | 31 +++++++++++++++++++++++++++++-- libi2pd_client/Torrents.h | 5 +++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 3a288b69..012b7b8d 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -1588,7 +1588,17 @@ namespace torrents ParseDictionary (std::string_view ((const char *)(buf + 1), len -1), [this](std::string_view key, std::string_view buf)->size_t { - if (key == "v") + if (key == "m") + { + return ParseDictionary (buf, [this](std::string_view msg, std::string_view msgID)->size_t + { + auto [id, l] = ExtractInteger (msgID); + if (l) + AddExtendedMsgHandler (msg, id); + return l; + }); + } + else if (key == "v") { auto [v, l] = ExtractByteString (buf); if (l) m_RemoteName = v; @@ -1598,7 +1608,19 @@ namespace torrents }); } else - LogPrint (eLogInfo, "Torrents: Unexecpetd extended message type ", (int)buf[0], " received"); + { + auto it = m_ExtendedMessageHandlers.find (buf[0]); + if (it != m_ExtendedMessageHandlers.end ()) + (this->*(it->second))(buf + 1, len - 1); + else + LogPrint (eLogInfo, "Torrents: Unexecpetd extended message type ", (int)buf[0], " received"); + } + } + + void PeerConnection::AddExtendedMsgHandler (std::string_view extensionName, int64_t msgID) + { + if (extensionName == "ut_metadata") + m_ExtendedMessageHandlers.emplace (msgID, &PeerConnection::HandleUtMetadataExtension); } void PeerConnection::SendExtendedMsg () @@ -1612,6 +1634,11 @@ namespace torrents WriteToStream (sendBuffer.data (), sendBuffer.size ()); } + void PeerConnection::HandleUtMetadataExtension (const uint8_t * buf, size_t len) + { + // TODO: + } + std::optional PeerConnection::GetNextBlockToRequest () { auto block = m_Torrent->GetNextBlockToRequest (shared_from_this (), true); // skip already requested pieces diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index a8d3cc75..89bde836 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -246,6 +246,8 @@ namespace torrents class TorrentsTunnel; class PeerConnection: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { + using ExtendedMessageHandler = void (PeerConnection::*)(const uint8_t * buf, size_t len); + public: using PeerID = std::array; @@ -315,6 +317,8 @@ namespace torrents void HandleChokeMsg (); void HandleExtendedMsg (const uint8_t * buf, size_t len); void SendExtendedMsg (); + void AddExtendedMsgHandler (std::string_view extensionName, int64_t msgID); + void HandleUtMetadataExtension (const uint8_t * buf, size_t len); // BEP9 std::optional GetNextBlockToRequest (); bool RequestNextBlocks (); @@ -336,6 +340,7 @@ namespace torrents std::list m_IncomingRequestsQueue; int m_LastRequestedPieceIndex; std::unique_ptr m_HandshakeReceiveTimer; + std::unordered_map m_ExtendedMessageHandlers; // stats uint64_t m_DownloadRate, m_UploadRate; // B/sec uint64_t m_LastBlockDownloadTimestamp, m_LastBlockUploadTimestamp; // monotonic milliseconds From b46554de6597ee9c7d7ab2f91af55fdac1be3928 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 16:12:40 -0400 Subject: [PATCH 16/38] correct incorrect block size for padding block --- libi2pd/NTCP2.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp index efbc29f2..f601d16b 100644 --- a/libi2pd/NTCP2.cpp +++ b/libi2pd/NTCP2.cpp @@ -1283,8 +1283,14 @@ namespace transport LogPrint (eLogDebug, "NTCP2: Block type ", (int)blk, " of size ", size); if (offset + size > len) { - LogPrint (eLogError, "NTCP2: Unexpected block length ", size); - break; + if (blk == eNTCP2BlkPadding) + // padding is always last block and we set size up to end of frame + size = len - offset; + else + { + LogPrint (eLogError, "NTCP2: Unexpected block length ", size, " of type ", (int)blk); + break; + } } switch (blk) { From 536b9e1ccaceeb3812763a919799e70a642e3e14 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 17:50:25 -0400 Subject: [PATCH 17/38] handle ut_metadata extended message --- libi2pd_client/Torrents.cpp | 74 +++++++++++++++++++++++++++++++++---- libi2pd_client/Torrents.h | 4 +- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 012b7b8d..d7ad1330 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -147,6 +147,13 @@ namespace torrents return ret; } + static std::string CreateInteger (int64_t v) + { + std::string ret ("i"); + ret += std::to_string (v); ret += "e"; + return ret; + } + static std::string CreateDictionary (const std::vector >& items) { std::stringstream s; @@ -398,8 +405,11 @@ namespace torrents return 0; }); if (!len) return 0; + // save info + m_Info.resize (len); + memcpy (m_Info.data (), (const uint8_t *)buf.data (), len); // calculate info hash - SHA1 ((const uint8_t *)buf.data (), len, m_InfoHash.data ()); + SHA1 (m_Info.data (), len, m_InfoHash.data ()); return len; } @@ -1613,7 +1623,7 @@ namespace torrents if (it != m_ExtendedMessageHandlers.end ()) (this->*(it->second))(buf + 1, len - 1); else - LogPrint (eLogInfo, "Torrents: Unexecpetd extended message type ", (int)buf[0], " received"); + LogPrint (eLogInfo, "Torrents: Unexpected extended message type ", (int)buf[0], " received"); } } @@ -1623,20 +1633,68 @@ namespace torrents m_ExtendedMessageHandlers.emplace (msgID, &PeerConnection::HandleUtMetadataExtension); } - void PeerConnection::SendExtendedMsg () + void PeerConnection::SendExtendedMsg (uint8_t extendedMsgID, std::string_view payload, std::string_view data) { - std::string payload = CreateDictionary ({{ CreateByteString ("v"), CreateByteString ("i2pd") }}); - std::vector sendBuffer (payload.length () + 1 + 5); - htobe32buf (sendBuffer.data (), payload.length () + 1 + 1); // length + std::string str; + if (!extendedMsgID) // handshake + { + str = CreateDictionary ({{ CreateByteString ("v"), CreateByteString ("i2pd") }}); + payload = str; + } + std::vector sendBuffer (payload.length () + data.length () + 1 + 5); + htobe32buf (sendBuffer.data (), payload.length () + data.length () + 1 + 1); // length sendBuffer[4] = eMessageTypeExtended; // msg ID - sendBuffer[5] = 0; // handshake + sendBuffer[5] = extendedMsgID; memcpy (sendBuffer.data () + 6, payload.data (), payload.size ()); + if (!data.empty ()) + memcpy (sendBuffer.data () + 6 + payload.size (), data.data (), data.size ()); WriteToStream (sendBuffer.data (), sendBuffer.size ()); } void PeerConnection::HandleUtMetadataExtension (const uint8_t * buf, size_t len) { - // TODO: + if (!m_Torrent) return; + int msgType = -1, piece = -1; + ParseDictionary (std::string_view ((const char *)buf, len), + [&msgType, &piece](std::string_view key, std::string_view buf)->size_t + { + if (key == "msg_type") + { + auto [value, l] = ExtractInteger (buf); + if (!l) msgType = value; + return l; + } + else if (key == "piece") + { + auto [value, l] = ExtractInteger (buf); + if (!l) piece = value; + return l; + } + return 0; + }); + if (msgType >=0 && piece >= 0) + { + switch (msgType) + { + case 0: + { + auto& info = m_Torrent->GetInfo (); + size_t offset = piece*REQUEST_BLOCK_SIZE; + if (offset < info.size ()) + { + size_t totalSize = std::min (info.size () - offset, REQUEST_BLOCK_SIZE); + SendExtendedMsg (3, // TODO: msgID from table + CreateDictionary ({{ CreateByteString ("msg_type"), CreateInteger (1) }, + { CreateByteString ("piece"), CreateInteger (piece) }, + { CreateByteString ("total_size"), CreateInteger (totalSize) } }), + std::string_view ((const char *)info.data () + offset, totalSize)); + } + break; + } + default: + LogPrint (eLogInfo, "Torrents: ut_metadata msg_type ", msgType, " is not supported"); + } + } } std::optional PeerConnection::GetNextBlockToRequest () diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 89bde836..54b51728 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -175,6 +175,7 @@ namespace torrents size_t GetLength () const { return m_Length; } size_t GetPieceLength () const { return m_PieceLength; } int GetInterval (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<1>(m_TrackerStats[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; } + const std::vector GetInfo () const { return m_Info; } const InfoHash& GetInfoHash () const { return m_InfoHash; } size_t GetLeft () const; std::string GetHexStringInfoHash () const; // in url format @@ -232,6 +233,7 @@ namespace torrents std::string m_Name, m_Announce; std::filesystem::path m_FullPath; size_t m_Length, m_PieceLength; + std::vector m_Info; // for BEP9 InfoHash m_InfoHash; // SHA1 std::vector m_Pieces; std::vector m_TrackerStats; @@ -316,7 +318,7 @@ namespace torrents void SendChokeMsg (); void HandleChokeMsg (); void HandleExtendedMsg (const uint8_t * buf, size_t len); - void SendExtendedMsg (); + void SendExtendedMsg (uint8_t extendedMsgID = 0, std::string_view payload = "", std::string_view data = ""); void AddExtendedMsgHandler (std::string_view extensionName, int64_t msgID); void HandleUtMetadataExtension (const uint8_t * buf, size_t len); // BEP9 From 59c756822d92a0d0a61341de7cdf347d97828dbc Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 18:08:55 -0400 Subject: [PATCH 18/38] fixed possible overflow --- libi2pd/Reseed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp index 4188f1c2..5e47b8c5 100644 --- a/libi2pd/Reseed.cpp +++ b/libi2pd/Reseed.cpp @@ -378,7 +378,7 @@ namespace data // take care about data descriptor if presented if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) { - size_t pos = s.tellg (); + auto pos = s.tellg (); if (!FindZipDataDescriptor (s)) { LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found"); From 8800014043944259f5699c6e96aa0b3baca8d472 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 19:57:45 -0400 Subject: [PATCH 19/38] use correct ident hash for datagram2 signature verification --- libi2pd/Datagram.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index dbc950f2..8428cd3d 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -267,7 +267,7 @@ namespace datagram return; } std::vector signedData (len + 32 - identityLen - signatureLen); - memcpy (signedData.data (), identity.GetIdentHash (), 32); + memcpy (signedData.data (), m_Owner->GetIdentity ()->GetIdentHash (), 32); memcpy (signedData.data () + 32, buf + identityLen, signedData.size () - 32); verified = transientVerifier ? transientVerifier->Verify (signedData.data (), signedData.size (), buf + len - signatureLen) : identity.Verify (signedData.data (), signedData.size (), buf + len - signatureLen); From 6d7ee819eb014f2ffd52935ce8aeb86f2805e32f Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 20:43:43 -0400 Subject: [PATCH 20/38] send ut_metadata in BEP10 handshake --- libi2pd_client/Torrents.cpp | 20 +++++++++++++------- libi2pd_client/Torrents.h | 4 ++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index d7ad1330..08a67c12 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -161,7 +161,7 @@ namespace torrents for (const auto& [name, value]: items) if (!name.empty () && !value.empty ()) { - s << name; s << value; + s << CreateByteString (name); s << value; } s << 'e'; return s.str (); @@ -1629,7 +1629,7 @@ namespace torrents void PeerConnection::AddExtendedMsgHandler (std::string_view extensionName, int64_t msgID) { - if (extensionName == "ut_metadata") + if (extensionName == EXTENSION_NAME_UT_METADATA) m_ExtendedMessageHandlers.emplace (msgID, &PeerConnection::HandleUtMetadataExtension); } @@ -1638,7 +1638,13 @@ namespace torrents std::string str; if (!extendedMsgID) // handshake { - str = CreateDictionary ({{ CreateByteString ("v"), CreateByteString ("i2pd") }}); + str = CreateDictionary ({ + { "m", CreateDictionary ({ + { EXTENSION_NAME_UT_METADATA, CreateInteger (EXTENSION_MSGID_UT_METADATA) } + }) }, + { "metadata_size", CreateInteger (m_Torrent->GetInfo ().size ()) }, + { "v", CreateByteString ("i2pd") } + }); payload = str; } std::vector sendBuffer (payload.length () + data.length () + 1 + 5); @@ -1683,10 +1689,10 @@ namespace torrents if (offset < info.size ()) { size_t totalSize = std::min (info.size () - offset, REQUEST_BLOCK_SIZE); - SendExtendedMsg (3, // TODO: msgID from table - CreateDictionary ({{ CreateByteString ("msg_type"), CreateInteger (1) }, - { CreateByteString ("piece"), CreateInteger (piece) }, - { CreateByteString ("total_size"), CreateInteger (totalSize) } }), + SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, + CreateDictionary ({{ "msg_type", CreateInteger (1) }, + { "piece", CreateInteger (piece) }, + { "total_size", CreateInteger (totalSize) } }), std::string_view ((const char *)info.data () + offset, totalSize)); } break; diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 54b51728..c481a727 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -53,6 +53,10 @@ namespace torrents constexpr size_t REQUEST_MSG_LENGTH = REQUEST_MSG_PAYLOAD_LENGTH + 5; constexpr size_t HAVE_MSG_PAYLOAD_LENGTH = 4; + // extensions + constexpr std::string_view EXTENSION_NAME_UT_METADATA { "ut_metadata" }; + constexpr uint8_t EXTENSION_MSGID_UT_METADATA = 1; + enum MessageType { eMessageTypeChoke = 0, From 421136b2646ff2eff2b80dc01afcba5af9f31fd4 Mon Sep 17 00:00:00 2001 From: orignal Date: Mon, 31 Aug 2026 21:51:30 -0400 Subject: [PATCH 21/38] accept both addresses ending with rpc or rpc/. Accept all content type starting with application/json --- libi2pd_client/TorrentsRPC.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index ca4b5158..611d77c4 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -539,7 +539,8 @@ namespace torrents auto tunnel = m_Server.GetTunnel ( {path.data (), path.size ()}); // boost::beast::string_view to std::string_view if (tunnel) { - if (m_Request[boost::beast::http::field::content_type] == "application/json" || + static constexpr std::string_view appjson { "application/json" }; + if (m_Request[boost::beast::http::field::content_type].compare (0, appjson.size (), appjson) || // starts with m_Request[boost::beast::http::field::content_type] == "application/x-www-form-urlencoded") { #ifdef JSON_SUPPORTED @@ -616,7 +617,10 @@ namespace torrents std::string rpcPath ("/"); if (!path.empty ()) rpcPath += std::string (path) + "/"; - rpcPath += "rpc/"; + // insert both pathes rpc and rpc/ + rpcPath += "rpc"; + m_Tunnels.emplace (rpcPath, tunnel); + rpcPath += "/"; m_Tunnels.emplace (rpcPath, tunnel); } From 557b8165101e380ce407ac79653d31a5f8a221d3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 08:34:51 -0400 Subject: [PATCH 22/38] use starts_with to check content-type if C++20 --- libi2pd_client/TorrentsRPC.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index 611d77c4..c0a1d71f 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -540,7 +540,11 @@ namespace torrents if (tunnel) { static constexpr std::string_view appjson { "application/json" }; - if (m_Request[boost::beast::http::field::content_type].compare (0, appjson.size (), appjson) || // starts with +#if __cplusplus >= 202002L // C++20 + if (m_Request[boost::beast::http::field::content_type].starts_with (appjson) || // starts with +#else + if (m_Request[boost::beast::http::field::content_type].substr (0, appjson.size ()) == appjson || // starts with +#endif m_Request[boost::beast::http::field::content_type] == "application/x-www-form-urlencoded") { #ifdef JSON_SUPPORTED From b8282d2d4c56c6d231e5471437d4a6481cda5b81 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 08:53:19 -0400 Subject: [PATCH 23/38] don't try to parse RPC request if not supported --- libi2pd_client/TorrentsRPC.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index c0a1d71f..58a38a0a 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -533,6 +533,7 @@ namespace torrents { if (!ecode) { +#ifdef JSON_SUPPORTED if (m_Request.method() == boost::beast::http::verb::post) { auto path = m_Request.target (); @@ -547,19 +548,17 @@ namespace torrents #endif m_Request[boost::beast::http::field::content_type] == "application/x-www-form-urlencoded") { -#ifdef JSON_SUPPORTED + JSONRPCHandler jsonHandler (tunnel); auto response = jsonHandler.HandleRequest (m_Request.body ()); if (!response.empty ()) SendResponse (boost::beast::http::status::ok, response); -#else - SendResponse (boost::beast::http::status::not_implemented, std::string ("Your boost version ") + BOOST_LIB_VERSION + " is too old"); -#endif } else SendResponse (boost::beast::http::status::no_content); } - else { + else + { LogPrint(eLogDebug, "TorrentRPC ", path, " not found ", tunnel); SendResponse (boost::beast::http::status::not_found); } @@ -568,6 +567,9 @@ namespace torrents SendResponse (boost::beast::http::status::no_content, "", true); else SendResponse (boost::beast::http::status::method_not_allowed); +#else + SendResponse (boost::beast::http::status::not_implemented, std::string ("Your boost version ") + BOOST_LIB_VERSION + " is too old"); +#endif } } From 39d6178f33ddbc0b4d4792c63be1afabb23b8210 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 15:26:24 -0400 Subject: [PATCH 24/38] handle BEP9 message --- libi2pd_client/Torrents.cpp | 49 ++++++++++++++++++++++++++++++++++--- libi2pd_client/Torrents.h | 3 +++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 08a67c12..7dd88883 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -854,7 +854,7 @@ namespace torrents m_Stream (stream), m_ReceiveBufferOffset (0), m_NextMsgLength (0), m_IsHandshakeSent (false), m_IsEstablished (false), m_IsChoked (true), m_IsRemoteChoked (true), m_IsInterested (false), m_IsRemoteInterested (false), m_LastReceiveTime (0), m_LastSendTime (0), - m_NumRequests (0), m_NumPieces (0), m_LastRequestedPieceIndex (-1), + m_NumRequests (0), m_NumPieces (0), m_LastRequestedPieceIndex (-1), m_RemoteMetadataSize (0), m_Downloaded (0), m_Uploaded (0) { ResetStats (); @@ -1608,6 +1608,12 @@ namespace torrents return l; }); } + else if (key == "metadata_size") + { + auto [s, l] = ExtractInteger (buf); + if (l) m_RemoteMetadataSize = s; + return l; + } else if (key == "v") { auto [v, l] = ExtractByteString (buf); @@ -1661,8 +1667,9 @@ namespace torrents { if (!m_Torrent) return; int msgType = -1, piece = -1; - ParseDictionary (std::string_view ((const char *)buf, len), - [&msgType, &piece](std::string_view key, std::string_view buf)->size_t + size_t size = 0; + auto payloadLen = ParseDictionary (std::string_view ((const char *)buf, len), + [&msgType, &piece, &size](std::string_view key, std::string_view buf)->size_t { if (key == "msg_type") { @@ -1676,13 +1683,19 @@ namespace torrents if (!l) piece = value; return l; } + else if (key == "total_size") + { + auto [s, l] = ExtractInteger (buf); + if (!l) size = s; + return l; + } return 0; }); if (msgType >=0 && piece >= 0) { switch (msgType) { - case 0: + case 0: // request { auto& info = m_Torrent->GetInfo (); size_t offset = piece*REQUEST_BLOCK_SIZE; @@ -1695,8 +1708,36 @@ namespace torrents { "total_size", CreateInteger (totalSize) } }), std::string_view ((const char *)info.data () + offset, totalSize)); } + else + SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (2) }, { "piece", CreateInteger (piece) }})); break; } + case 1: // data + { + if (payloadLen + size > len) break; + size_t offset = piece*REQUEST_BLOCK_SIZE; + if (offset == m_RemoteMetadata.size () && size) + { + m_RemoteMetadata.resize (m_RemoteMetadata.size () + size); + memcpy (m_RemoteMetadata.data () + offset,buf + payloadLen, size); + if (m_RemoteMetadata.size () < m_RemoteMetadataSize) + // request next piece + SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (0) }, { "piece", CreateInteger (piece + 1) }})); + else + { + // all info received + LogPrint (eLogDebug, "Torrents: ut_metadata ", m_RemoteMetadataSize, " bytes of info received"); + // TODO: update torrent + // cleanup + std::vector tmp; + m_RemoteMetadata.swap (tmp); + } + } + break; + } + case 2: // reject + LogPrint (eLogError, "Torrents: ut_metadata piece ", piece, " request rejected"); + break; default: LogPrint (eLogInfo, "Torrents: ut_metadata msg_type ", msgType, " is not supported"); } diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index c481a727..b932400b 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -347,6 +347,9 @@ namespace torrents int m_LastRequestedPieceIndex; std::unique_ptr m_HandshakeReceiveTimer; std::unordered_map m_ExtendedMessageHandlers; + // BEP9 + size_t m_RemoteMetadataSize; + std::vector m_RemoteMetadata; // stats uint64_t m_DownloadRate, m_UploadRate; // B/sec uint64_t m_LastBlockDownloadTimestamp, m_LastBlockUploadTimestamp; // monotonic milliseconds From 7d69f74e29d4f33edbe47fb0790095faaac204f3 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 16:37:16 -0400 Subject: [PATCH 25/38] retrun relative pacth to torrent's files --- libi2pd_client/TorrentsRPC.cpp | 62 ++++++++++++++++++--------------- libi2pd_client/TorrentsTunnel.h | 1 + 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index 58a38a0a..45041a54 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -54,6 +54,7 @@ namespace torrents boost::json::array GetPeers (std::shared_ptr torrent) const; boost::json::array GetTrackers (std::shared_ptr torrent) const; boost::json::array GetTrackerStats (std::shared_ptr torrent) const; + boost::json::array GetFiles (std::shared_ptr torrent) const; static std::string_view RecognizeClientByPeerID (const PeerConnection::PeerID& peerID); std::string HandleTorrentAdd (boost::json::object&& jsonRequest); @@ -244,6 +245,8 @@ namespace torrents t["trackers"] = GetTrackers (torrent); else if (field == "trackerStats") t["trackerStats"] = GetTrackerStats (torrent); + else if (field == "files") + t["files"] = GetFiles (torrent); else { auto fieldValue = GetFieldValue (field.as_string (), torrent); @@ -296,35 +299,6 @@ namespace torrents return boost::json::value(b64pieces); } }, - { "files", [](std::shared_ptr torrent) - { - boost::json::array files; - const auto& torrentFiles = torrent->GetFiles (); - if (torrentFiles.empty ()) - { - boost::json::object file; - file["name"] = torrent->GetFullPath ().string (); - file["length"] = torrent->GetLength (); - file["bytesCompleted"] = torrent->GetLength () - torrent->GetLeft (); - files.push_back (file); - } - else - { - auto filesCompleted = torrent->GetFilesCompleted (); - size_t ind = 0; - for (const auto& [filePath, fileSize]: torrentFiles) - { - boost::json::object file; - file["name"] = filePath.string (); - file["length"] = fileSize; - file["bytesCompleted"] = (ind < filesCompleted.size ()) ? filesCompleted[ind] : 0; - files.push_back (file); - ind++; - } - } - return files; - } - }, { "wanted", [](std::shared_ptr torrent) { boost::json::array wanted; @@ -470,6 +444,36 @@ namespace torrents return trackers; } + boost::json::array JSONRPCHandler::GetFiles (std::shared_ptr torrent) const + { + boost::json::array files; + const auto& torrentFiles = torrent->GetFiles (); + if (torrentFiles.empty ()) + { + boost::json::object file; + file["name"] = torrent->GetName (); + file["length"] = torrent->GetLength (); + file["bytesCompleted"] = torrent->GetLength () - torrent->GetLeft (); + files.push_back (file); + } + else + { + const auto& torrentsDir = m_Tunnel->GetTorrentsDir (); + auto filesCompleted = torrent->GetFilesCompleted (); + size_t ind = 0; + for (const auto& [filePath, fileSize]: torrentFiles) + { + boost::json::object file; + file["name"] = std::filesystem::relative (filePath, torrentsDir).string (); + file["length"] = fileSize; + file["bytesCompleted"] = (ind < filesCompleted.size ()) ? filesCompleted[ind] : 0; + files.push_back (file); + ind++; + } + } + return files; + } + std::string_view JSONRPCHandler::RecognizeClientByPeerID (const PeerConnection::PeerID& peerID) { static constexpr std::array i2psnark { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03 }; diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index a7dcaa49..755b5714 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -90,6 +90,7 @@ namespace torrents auto& GetDiskIOService () { return m_DiskIOService.GetService (); }; const std::string& GetPeerID () const { return m_PeerID; } + const std::filesystem::path& GetTorrentsDir () const { return m_TorrentsDir; } std::string GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : ""; } size_t GetNumTrackers () const { return m_Trackers.size (); } std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const; From 286e10f8fcdaf83a87ef37949f85eda36eaa4e2d Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 17:47:19 -0400 Subject: [PATCH 26/38] send commpressed datagrams right away --- libi2pd_client/TorrentsTunnel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 72ad7cb7..091a1239 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -46,7 +46,7 @@ namespace torrents i2p::client::I2PService::Start (); m_DiskIOService.Start (); - auto dgramDest = GetLocalDestination ()->CreateDatagramDestination (false, i2p::datagram::eDatagramV3); + auto dgramDest = GetLocalDestination ()->CreateDatagramDestination (true, i2p::datagram::eDatagramV3); if (dgramDest) dgramDest->SetRawReceiver (std::bind (&TorrentsTunnel::HandleRecvFromI2PRaw, std::static_pointer_cast(shared_from_this ()), @@ -871,6 +871,7 @@ namespace torrents trackerID, fromPort, std::shared_ptr(nullptr), i2p::util::GetMonotonicMilliseconds ())); session->SetVersion (i2p::datagram::eDatagramV2); // send datagram2 dgramDest->SendDatagram (session, connectRequest, 16, fromPort, port); + dgramDest->FlushSendQueue (session); } else LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session to ", dest); @@ -953,6 +954,7 @@ namespace torrents trackerID, fromPort, torrent, i2p::util::GetMonotonicMilliseconds ())); session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3 dgramDest->SendDatagram (session, announce, 98, fromPort, port); + dgramDest->FlushSendQueue (session); } else LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session"); From dc1a0ebe941f8a8b66e8ce85819f117bc8f2b2ed Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:37:06 +0300 Subject: [PATCH 27/38] don't send packets from a terminated stream --- libi2pd/Streaming.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp index d59ab3a5..6afddfce 100644 --- a/libi2pd/Streaming.cpp +++ b/libi2pd/Streaming.cpp @@ -1531,6 +1531,9 @@ namespace stream void Stream::SendPackets (const std::list& packets) { + // the local destination is a reference and it can be gone already: a handler + // queued before the destination was stopped runs after it + if (m_Status == eStreamStatusTerminated) return; if (!m_RemoteLeaseSet) { CancelRemoteLeaseChange (); From 778e8344e9442826879f9324f0b3a2f0b413a716 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 19:53:12 -0400 Subject: [PATCH 28/38] fixed typo --- libi2pd_client/Torrents.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 7dd88883..df5e8dd8 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -1674,19 +1674,19 @@ namespace torrents if (key == "msg_type") { auto [value, l] = ExtractInteger (buf); - if (!l) msgType = value; + if (l) msgType = value; return l; } else if (key == "piece") { auto [value, l] = ExtractInteger (buf); - if (!l) piece = value; + if (l) piece = value; return l; } else if (key == "total_size") { auto [s, l] = ExtractInteger (buf); - if (!l) size = s; + if (l) size = s; return l; } return 0; From 920226d67a6f2a1a396b672b77eb7b1c69b4d564 Mon Sep 17 00:00:00 2001 From: orignal Date: Tue, 1 Sep 2026 20:38:41 -0400 Subject: [PATCH 29/38] verify received torrent info with infoHash --- libi2pd_client/Torrents.cpp | 14 +++++++++++++- libi2pd_client/Torrents.h | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index df5e8dd8..4ca03555 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -329,11 +329,15 @@ namespace torrents } } - Torrent::Torrent (std::string_view buf): + Torrent::Torrent (): m_Length (0), m_PieceLength (0), m_IsComplete (false), m_IsStopped (false), m_Uploaded (0), m_Downloaded (0) { ResetStats (); + } + + Torrent::Torrent (std::string_view buf): Torrent () + { ParseDictionary (buf, [this](std::string_view key, std::string_view buf)->size_t { if (key == "announce") @@ -348,6 +352,10 @@ namespace torrents }); } + Torrent::Torrent (const InfoHash& infoHash): Torrent () + { + m_InfoHash = infoHash; + } size_t Torrent::ParsePieces (std::string_view buf) { @@ -1727,6 +1735,10 @@ namespace torrents { // all info received LogPrint (eLogDebug, "Torrents: ut_metadata ", m_RemoteMetadataSize, " bytes of info received"); + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1 (m_RemoteMetadata.data (), m_RemoteMetadata.size (), digest); + if (memcmp (m_Torrent->GetInfoHash ().data (), digest, SHA_DIGEST_LENGTH)) + LogPrint (eLogError, "Torrents: ut_metadata info doesn't match infoHash"); // TODO: update torrent // cleanup std::vector tmp; diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index b932400b..559080cf 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -156,9 +156,10 @@ namespace torrents // seeders, leechers, last update time in seconds since epoch, error) public: - using InfoHash = std::array; + using InfoHash = std::array; Torrent (std::string_view buf); + Torrent (const InfoHash& infoHash); void ParseTrackerResponse (size_t trackerID, std::string_view buf); void HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval, const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers); @@ -225,6 +226,7 @@ namespace torrents private: + Torrent (); size_t ParsePieces (std::string_view buf); size_t ParseInfo (std::string_view buf); size_t ParsePeers (size_t trackerID, std::string_view buf); From 620e51abbc92ca259b112ceebba33f681852c732 Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Wed, 2 Sep 2026 03:46:25 +0300 Subject: [PATCH 30/38] don't reset shared receivers from a tunnel that was never started --- libi2pd_client/UDPTunnel.cpp | 9 +++++++++ libi2pd_client/UDPTunnel.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp index 0749b37e..a2076df3 100644 --- a/libi2pd_client/UDPTunnel.cpp +++ b/libi2pd_client/UDPTunnel.cpp @@ -483,6 +483,7 @@ namespace client void I2PUDPServerTunnel::Start () { + m_IsStarted = true; m_LocalDest->Start (); auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); @@ -553,6 +554,11 @@ namespace client void I2PUDPServerTunnel::Stop () { + // a config reload builds a second tunnel object and drops it when the same + // one is already there. Its destructor must not reset the receiver of the + // live tunnel: they share the datagram destination + if (!m_IsStarted) return; + m_IsStarted = false; if (m_StatsTimer) m_StatsTimer->cancel (); if (m_CleanupTimer) m_CleanupTimer->cancel (); auto dgram = m_LocalDest->GetDatagramDestination (); @@ -609,6 +615,7 @@ namespace client void I2PUDPClientTunnel::Start () { + m_IsStarted = true; UDPConnection::Start (); // Reset flag in case of tunnel reload if (m_cancel_resolve) m_cancel_resolve = false; @@ -643,6 +650,8 @@ namespace client void I2PUDPClientTunnel::Stop () { + if (!m_IsStarted) return; // see the comment in I2PUDPServerTunnel::Stop + m_IsStarted = false; if (m_KeepAliveTimer) m_KeepAliveTimer->cancel (); if (m_StatsTimer) m_StatsTimer->cancel (); diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h index 3b77615f..8c0e522d 100644 --- a/libi2pd_client/UDPTunnel.h +++ b/libi2pd_client/UDPTunnel.h @@ -194,6 +194,7 @@ namespace client UDPSessionPtr m_LastSession; uint16_t m_inPort; bool m_Gzip; + bool m_IsStarted = false; // a tunnel that never started shares nothing to tear down uint32_t m_NumRawNoSession = 0; size_t m_MaxWindow = I2P_UDP_MIN_MAX_NUM_UNACKED_DATAGRAMS; std::unique_ptr m_StatsTimer; @@ -264,6 +265,7 @@ namespace client uint16_t RemotePort, m_LastPort; bool m_cancel_resolve; bool m_Gzip; + bool m_IsStarted = false; // see the comment in I2PUDPServerTunnel i2p::datagram::DatagramVersion m_DatagramVersion; std::shared_ptr m_LastSession; uint32_t m_KeepAliveInterval = 0; From 78d1794b5b5a6b913d3ac05b550a59e94f9e8068 Mon Sep 17 00:00:00 2001 From: orignal Date: Wed, 2 Sep 2026 11:10:10 -0400 Subject: [PATCH 31/38] NO_TORRENTS compilation param added --- daemon/HTTPServer.cpp | 6 ++++++ libi2pd_client/ClientContext.cpp | 11 ++++++++++- libi2pd_client/ClientContext.h | 6 ++++++ libi2pd_client/Torrents.cpp | 4 ++++ libi2pd_client/Torrents.h | 5 +++++ libi2pd_client/TorrentsRPC.cpp | 4 ++++ libi2pd_client/TorrentsRPC.h | 4 ++++ libi2pd_client/TorrentsTunnel.cpp | 4 ++++ libi2pd_client/TorrentsTunnel.h | 4 ++++ 9 files changed, 47 insertions(+), 1 deletion(-) diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index bb24b48f..410ee86f 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -1206,6 +1206,7 @@ namespace http { } s << "\r\n
\r\n"; } +#ifndef NO_TORRENTS auto& torrentsTunnels = i2p::client::context.GetTorrentsTunnels (); if (!torrentsTunnels.empty ()) { @@ -1223,10 +1224,12 @@ namespace http { } s << "\r\n
\r\n"; } +#endif } static void ShowTorrentsPage (std::stringstream& s, std::map& params) { +#ifndef NO_TORRENTS bool js; i2p::config::GetOption("http.javascript", js); if (!js) { @@ -1284,6 +1287,9 @@ namespace http { } else s << "

Not found rpc tunnel " << tunnelname << "

\r\n"; +#else + s << "

Torrents are not supported

\r\n"; +#endif } HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp index d5089e78..2a60cd29 100644 --- a/libi2pd_client/ClientContext.cpp +++ b/libi2pd_client/ClientContext.cpp @@ -117,9 +117,11 @@ namespace client m_AddressBook.StartResolvers (); +#ifndef NO_TORRENTS // start torrents RPC server for (auto& it: m_TorrentsRPCServers) it.second->Start (); +#endif } std::shared_ptr ClientContext::DetachHttpProxy () @@ -168,6 +170,7 @@ namespace client } m_ServerTunnels.clear (); +#ifndef NO_TORRENTS // start torrents RPC server for (auto& it: m_TorrentsRPCServers) it.second->Stop (); @@ -178,6 +181,7 @@ namespace client it.second->Stop (); } m_TorrentsTunnels.clear (); +#endif if (m_SamBridge) { @@ -980,7 +984,7 @@ namespace client } else if (type == I2P_TUNNELS_SECTION_TYPE_TORRENTS) { - +#ifndef NO_TORRENTS // mandatory params std::string torrentsDir = section.second.get (TORRENTS_TUNNEL_TORRENTS_DIR); // optional params @@ -1038,6 +1042,9 @@ namespace client } else LogPrint (eLogError, "Clients: Failed to create torrents tunnel ", name, ". Duplicate keys ", keys); +#else + LogPrint (eLogError, "Clients: Torrents tunnels are not supported. Comppile without NO_TORRENTS"); +#endif } else LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf); @@ -1216,6 +1223,7 @@ namespace client } } +#ifndef NO_TORRENTS std::shared_ptr ClientContext::CreateTorrentsRPCServer ( std::string_view address, uint16_t port) { @@ -1238,5 +1246,6 @@ namespace client } return nullptr; } +#endif } } diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index 663a3276..f9e37925 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -152,7 +152,9 @@ namespace client void CreateNewSharedLocalDestination (); void AddLocalDestination (std::shared_ptr localDestination); +#ifndef NO_TORRENTS std::shared_ptr CreateTorrentsRPCServer (std::string_view address, uint16_t port); +#endif private: @@ -171,8 +173,10 @@ namespace client std::map > m_ClientForwards; // local endpoint -> udp tunnel std::map, std::shared_ptr > m_ServerForwards; // -> udp tunnel +#ifndef NO_TORRENTS std::unordered_map > m_TorrentsTunnels; // local destination -> torrents tunnel std::unordered_map > m_TorrentsRPCServers; +#endif SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; @@ -194,8 +198,10 @@ namespace client const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } +#ifndef NO_TORRENTS const decltype(m_TorrentsTunnels)& GetTorrentsTunnels () const { return m_TorrentsTunnels; } const decltype(m_TorrentsRPCServers)& GetTorrentsRPCServers () const { return m_TorrentsRPCServers; } +#endif std::shared_ptr GetHttpProxy () const { std::lock_guard l(m_ProxyMutex); diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 4ca03555..e08739d0 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -6,6 +6,8 @@ * See full license text in LICENSE file at top of project tree */ +#ifndef NO_TORRENTS + #include #include #include @@ -1798,3 +1800,5 @@ namespace torrents } } } + +#endif // NO_TORRENTS diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 559080cf..92729482 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -9,6 +9,8 @@ #ifndef TORRENTS_H__ #define TORRENTS_H__ +#ifndef NO_TORRENTS + #include #include #include @@ -360,4 +362,7 @@ namespace torrents }; } } + +#endif // NO_TORRENTS + #endif diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index 45041a54..90c37070 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -6,6 +6,8 @@ * See full license text in LICENSE file at top of project tree */ +#ifndef NO_TORRENTS + #include #if !defined(ANDROID) && (BOOST_VERSION >= 108100) // boost::json since 1.75, we allow it since 1.81 due to std::string_view compatibility #include @@ -670,3 +672,5 @@ namespace torrents } } } + +#endif diff --git a/libi2pd_client/TorrentsRPC.h b/libi2pd_client/TorrentsRPC.h index a04fdb04..342ef878 100644 --- a/libi2pd_client/TorrentsRPC.h +++ b/libi2pd_client/TorrentsRPC.h @@ -9,6 +9,8 @@ #ifndef TORRENTS_RPC_H__ #define TORRENTS_RPC_H__ +#ifndef NO_TORRENTS + #include #include #include @@ -76,3 +78,5 @@ namespace torrents } #endif + +#endif diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 091a1239..7f079070 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -6,6 +6,8 @@ * See full license text in LICENSE file at top of project tree */ +#ifndef NO_TORRENTS + #include #include #include @@ -991,3 +993,5 @@ namespace torrents } } } + +#endif // NO_TORRENTS diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index 755b5714..dafc5f36 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -6,6 +6,8 @@ * See full license text in LICENSE file at top of project tree */ +#ifndef NO_TORRENTS + #ifndef TORRENTS_TUNNEL_H__ #define TORRENTS_TUNNEL_H__ @@ -167,3 +169,5 @@ namespace torrents } #endif + +#endif // NO_TORRENTS From 789fb8b9c119466603e2f4c5cfe4ef37b0ee750a Mon Sep 17 00:00:00 2001 From: PobreGato <315121269+pobregat0@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:58:09 +0300 Subject: [PATCH 32/38] check datagram sessions for empty under the lock --- libi2pd/Datagram.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp index 8428cd3d..9fb7776e 100644 --- a/libi2pd/Datagram.cpp +++ b/libi2pd/Datagram.cpp @@ -488,10 +488,10 @@ namespace datagram void DatagramDestination::CleanUp () { - if (m_Sessions.empty ()) return; auto now = i2p::util::GetMillisecondsSinceEpoch(); - LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); std::unique_lock lock(m_SessionsMutex); + if (m_Sessions.empty ()) return; + LogPrint(eLogDebug, "DatagramDestination: clean up sessions"); // for each session ... for (auto it = m_Sessions.begin (); it != m_Sessions.end (); ) { From 0b0e88a0d022124cbf129c5836ef99451c22782b Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Sep 2026 15:08:07 -0400 Subject: [PATCH 33/38] correct file completion size calculation --- libi2pd_client/Torrents.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index e08739d0..bd124178 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -814,10 +814,18 @@ namespace torrents } else { - if (piece.IsComplete ()) currentCompletedSize += (filesIT->second - currentSize); + size_t leftoverSize = filesIT->second - currentSize; + if (piece.IsComplete ()) currentCompletedSize += leftoverSize; completed.push_back (currentCompletedSize); - currentSize = 0; currentCompletedSize = 0; + currentSize = m_PieceLength - leftoverSize; filesIT++; + while (filesIT != m_Files.end () && filesIT->second <= currentSize) + { + completed.push_back (filesIT->second); + currentSize -= filesIT->second; + filesIT++; + } + currentCompletedSize = piece.IsComplete () ? currentSize : 0; if (filesIT == m_Files.end ()) break; } } From e49079f165c9c51e0e3781167fe93319406f8cd7 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Sep 2026 18:31:46 -0400 Subject: [PATCH 34/38] handle magnet links from torrent-add RPC --- libi2pd_client/TorrentsRPC.cpp | 44 ++++++++++++++---------- libi2pd_client/TorrentsTunnel.cpp | 56 +++++++++++++++++++++++++++++++ libi2pd_client/TorrentsTunnel.h | 3 +- 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index 90c37070..7a262fa2 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -48,7 +48,7 @@ namespace torrents private: std::string SuccessResponse (int64_t id, boost::json::object&& arguments); - std::string ResultResponse (int64_t id, boost::json::object&& result); + std::string ResultResponse (int64_t id, boost::json::value&& result); std::string ErrorResponse (JSONRPCErrorCode errorCode, int64_t id, std::string_view message); static int64_t GetTag (boost::json::object& jsonRequest); static std::vector GetTorrentIds (boost::json::object& arguments); @@ -79,7 +79,7 @@ namespace torrents return boost::json::serialize(response); } - std::string JSONRPCHandler::ResultResponse (int64_t id, boost::json::object&& result) + std::string JSONRPCHandler::ResultResponse (int64_t id, boost::json::value&& result) { boost::json::object response; response["jsonrpc"] = "2.0"; @@ -169,32 +169,40 @@ namespace torrents std::string JSONRPCHandler::HandleTorrentAdd (boost::json::object&& jsonRequest) { auto arguments = jsonRequest.at ("arguments").as_object (); - auto b64torrent = arguments.at ("metainfo").as_string (); - std::string torrentFileContent; - torrentFileContent.resize (boost::beast::detail::base64::decoded_size (b64torrent.size ())); - boost::beast::detail::base64::decode (torrentFileContent.data (), b64torrent.data (), b64torrent.size ()); - auto [torrent, id] = m_Tunnel->AddTorrent (torrentFileContent); + std::shared_ptr torrent; int id = 0; + if (arguments.contains ("metainfo")) + { + auto b64torrent = arguments.at ("metainfo").as_string (); + std::string torrentFileContent; + torrentFileContent.resize (boost::beast::detail::base64::decoded_size (b64torrent.size ())); + boost::beast::detail::base64::decode (torrentFileContent.data (), b64torrent.data (), b64torrent.size ()); + std::tie (torrent, id) = m_Tunnel->AddTorrent (torrentFileContent); + } + else if (arguments.contains ("filename")) + std::tie (torrent, id) = m_Tunnel->AddMagnet (arguments.at ("filename").as_string ()); - boost::json::object response, torrentInfo; if (torrent) { + boost::json::object response, torrentInfo; std::string hexHash; boost::algorithm::hex (torrent->GetInfoHash ().begin(), torrent->GetInfoHash ().end(), std::back_inserter(hexHash)); torrentInfo["id"] = id; torrentInfo["hashString"] = hexHash; torrentInfo["name"] = torrent->GetName (); - } - if (id) - { - response["torrent-added"] = torrentInfo; - LogPrint (eLogDebug, "TorrentsRPC: torrent added ", torrentInfo["name"].as_string ()); + if (id) + { + response["torrent-added"] = torrentInfo; + LogPrint (eLogDebug, "TorrentsRPC: torrent added ", torrentInfo["name"].as_string ()); + } + else + { + response["torrent-duplicate"] = torrentInfo; + LogPrint (eLogDebug, "TorrentsRPC: duplicate torrent ", torrentInfo["name"].as_string ()); + } + return SuccessResponse (GetTag (jsonRequest), std::move (response)); } else - { - response["torrent-duplicate"] = torrentInfo; - LogPrint (eLogDebug, "TorrentsRPC: duplicate torrent ", torrentInfo["name"].as_string ()); - } - return SuccessResponse (GetTag (jsonRequest), std::move (response)); + return ResultResponse (GetTag (jsonRequest), boost::json::string ("invalid or corrupt torrent file")); } std::string JSONRPCHandler::HandleTorrentRemove (boost::json::object&& jsonRequest) diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index 7f079070..f127bbf7 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include "Log.h" @@ -328,6 +329,61 @@ namespace torrents return { torrent, 0 }; } + std::pair, int> TorrentsTunnel::AddMagnet (std::string_view magnet) + { + // magnet:?xt=urn:btih:&dn=&tr= + static constexpr std::string_view magnetPrefix { "magnet:?" }; +#if __cplusplus >= 202002L // C++20 + if (magnet.starts_with (magnetPrefix)) +#else + if (magnet.substr (0, magnetPrefix.size ()) == magnetPrefix) +#endif + { + Torrent::InfoHash infoHash; + bool isInfoHashFound = false; + magnet = magnet.substr (magnetPrefix.size ()); + while (!magnet.empty()) + { + auto pos = magnet.find ('&'); + std::string_view param; + if (pos != std::string::npos) + { + param = magnet.substr (0, pos - 1); + magnet = magnet.substr (pos + 1); + } + else + { + param = magnet; + magnet = ""; + } + static constexpr std::string_view hashPrefix { "xt=urn:btih:" }; +#if __cplusplus >= 202002L // C++20 + if (param.starts_with (hashPrefix)) +#else + if (param.substr (0, hashPrefix.size ()) == hashPrefix) +#endif + { + std::string_view hexStr = param.substr (hashPrefix.size ()); + try + { + boost::algorithm::unhex (hexStr.begin(), hexStr.end(), infoHash.begin()); + isInfoHashFound = true; + } + catch (std::exception& ex) + { + LogPrint (eLogInfo, "TorentsTunnel: Can't unhex magnet hash ", hexStr); + } + } + } + if (isInfoHashFound && m_Torrents.find (infoHash) == m_Torrents.end ()) + { + auto torrent = std::make_shared (infoHash); + return { torrent, InsertTorrent (torrent) }; + } + } + return { nullptr, 0 }; + } + int TorrentsTunnel::InsertTorrent (std::shared_ptr torrent) { if (!torrent) return 0; diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index dafc5f36..6c0eb874 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -98,7 +98,8 @@ namespace torrents std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const; std::shared_ptr FindTorrentByID (int id) const; std::vector GetTorrentIDs () const; - std::pair, int> AddTorrent (std::string_view torrentFileContent); // (tunnel, id) + std::pair, int> AddTorrent (std::string_view torrentFileContent); // retrun (torrent, id) + std::pair, int> AddMagnet (std::string_view magnet); // return (torrent, id) bool RemoveTorrent (int id, bool deleteFiles); bool StopTorrent (int id); bool StartTorrent (int id); From d608d3f5b2903fed8a67238515c3aac64ed3a1b3 Mon Sep 17 00:00:00 2001 From: orignal Date: Thu, 3 Sep 2026 20:25:45 -0400 Subject: [PATCH 35/38] show torrent availability on remote peer in progress field --- libi2pd_client/TorrentsRPC.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index 7a262fa2..d23ad40d 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -385,7 +385,8 @@ namespace torrents peer["clientIsIntersted"] = it->IsInterested (); peer["peerIsInterested"] = it->IsRemoteInterested (); peer["flagStr"] = flags; - peer["progress"] = torrent->GetLength () ? ((float)it->GetDownloaded ())/(float)(torrent->GetLength ()) : 1.0; + const auto& remoteBitfield = it->GetRemoteBitfield (); + peer["progress"] = remoteBitfield.size () ? ((float)remoteBitfield.count ())/((float)remoteBitfield.size ()) : 0.0; peers.push_back (peer); } } From d203db13a74b6382923fdccc59c9e8d9f4b16034 Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Sep 2026 17:36:28 -0400 Subject: [PATCH 36/38] update torrent info and save .torent file from BEP9 metadata --- libi2pd_client/Torrents.cpp | 19 ++++++++---- libi2pd_client/Torrents.h | 3 +- libi2pd_client/TorrentsTunnel.cpp | 49 +++++++++++++++++++++++++------ libi2pd_client/TorrentsTunnel.h | 2 ++ 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index bd124178..4c6b7aa2 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -373,7 +373,6 @@ namespace torrents return len; } - size_t Torrent::ParseInfo (std::string_view buf) { size_t len = ParseDictionary (buf, [this](std::string_view key, std::string_view buf)->size_t @@ -423,6 +422,12 @@ namespace torrents return len; } + std::string Torrent::CreateTorrentFileContent () const + { + if (m_Info.empty ()) return ""; + return CreateDictionary ( {{ "info", std::string_view ((const char *)m_Info.data (), m_Info.size ()) }} ); + } + bool Torrent::IsSafeName (std::string_view name) { if (name.empty () || name == "." || name == "..") return false; @@ -1279,7 +1284,7 @@ namespace torrents void PeerConnection::HandleBitfieldMsg (const uint8_t * buf, size_t len) { - if (!m_Torrent) return; + if (!m_Torrent || !m_Torrent->GetLength ()) return; // we are magnet and don't have torrent info yet m_IsInterested = false; size_t numPieces = m_Torrent->GetNumPieces (); m_RemoteBitfield.resize (numPieces); @@ -1640,6 +1645,9 @@ namespace torrents } return 0; }); + if (!m_Torrent->GetLength () && m_RemoteMetadataSize) // torrent is magnet and peer supports BEP9 + // request first piece of info + SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (0) }, { "piece", CreateInteger (0) }})); } else { @@ -1748,11 +1756,10 @@ namespace torrents uint8_t digest[SHA_DIGEST_LENGTH]; SHA1 (m_RemoteMetadata.data (), m_RemoteMetadata.size (), digest); if (memcmp (m_Torrent->GetInfoHash ().data (), digest, SHA_DIGEST_LENGTH)) + GetTorrentsTunnel ()->UpdateTorrentInfo (m_Torrent, std::string_view ((const char *)m_RemoteMetadata.data (), m_RemoteMetadata.size ())); + else LogPrint (eLogError, "Torrents: ut_metadata info doesn't match infoHash"); - // TODO: update torrent - // cleanup - std::vector tmp; - m_RemoteMetadata.swap (tmp); + Close (); // we need to reconnect to receive bitfield } } break; diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 92729482..99f54ccf 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -162,6 +162,8 @@ namespace torrents Torrent (std::string_view buf); Torrent (const InfoHash& infoHash); + size_t ParseInfo (std::string_view buf); + std::string CreateTorrentFileContent () const; void ParseTrackerResponse (size_t trackerID, std::string_view buf); void HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval, const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers); @@ -230,7 +232,6 @@ namespace torrents Torrent (); size_t ParsePieces (std::string_view buf); - size_t ParseInfo (std::string_view buf); size_t ParsePeers (size_t trackerID, std::string_view buf); size_t ParseFiles (std::string_view buf); static bool IsSafeName (std::string_view name); // a name from a torrent file before it becomes a path diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index f127bbf7..fbcca43e 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -134,6 +134,20 @@ namespace torrents } } + void TorrentsTunnel::SaveTorrentFile (std::shared_ptr torrent) + { + if (!torrent) return; + auto torrentFilePath = torrent->GetFullPath (); torrentFilePath += ".torrent"; + std::ofstream f(torrentFilePath, std::ofstream::binary); + if (f) + { + auto content = torrent->CreateTorrentFileContent (); + f.write (content.data (), content.size ()); + } + else + LogPrint (eLogError, "TorrentsTunnel: Can't open ", torrentFilePath); + } + void TorrentsTunnel::InitTorrentFiles (std::shared_ptr torrent) { if (!torrent) return; @@ -315,15 +329,20 @@ namespace torrents if (m_Torrents.find (torrent->GetInfoHash ()) == m_Torrents.end ()) { torrent->SetFullPath (m_TorrentsDir/std::filesystem::path (torrent->GetName ())); - { - auto torrentFilePath = torrent->GetFullPath (); torrentFilePath += ".torrent"; - std::ofstream f(torrentFilePath, std::ofstream::binary); - if (f) - f.write (torrentFileContent.data (), torrentFileContent.size ()); - else - return { torrent, 0 }; - } - InitTorrentFiles (torrent); + std::string content (torrentFileContent); + boost::asio::post (GetDiskIOService (), [this, torrent, content]() + { + auto torrentFilePath = torrent->GetFullPath (); torrentFilePath += ".torrent"; + { + // TODO: replace by SaveTorrentFile + std::ofstream f(torrentFilePath, std::ofstream::binary); + if (f) + f.write (content.data (), content.size ()); + else + LogPrint (eLogError, "TorrentsTunnel: Can't open ", torrentFilePath); + } + InitTorrentFiles (torrent); + }); return { torrent, InsertTorrent (torrent) }; } return { torrent, 0 }; @@ -502,6 +521,18 @@ namespace torrents return true; } + void TorrentsTunnel::UpdateTorrentInfo (std::shared_ptr torrent, std::string_view info) + { + if (!torrent) return; + torrent->ParseInfo (info); + torrent->SetFullPath (m_TorrentsDir/std::filesystem::path (torrent->GetName ())); + boost::asio::post (GetDiskIOService (), [this, torrent]() + { + SaveTorrentFile (torrent); + InitTorrentFiles (torrent); + }); + } + void TorrentsTunnel::Accept () { auto localDestination = GetLocalDestination (); diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h index 6c0eb874..1c4a942b 100644 --- a/libi2pd_client/TorrentsTunnel.h +++ b/libi2pd_client/TorrentsTunnel.h @@ -100,6 +100,7 @@ namespace torrents std::vector GetTorrentIDs () const; std::pair, int> AddTorrent (std::string_view torrentFileContent); // retrun (torrent, id) std::pair, int> AddMagnet (std::string_view magnet); // return (torrent, id) + void UpdateTorrentInfo (std::shared_ptr torrent, std::string_view info); // magnet bool RemoveTorrent (int id, bool deleteFiles); bool StopTorrent (int id); bool StartTorrent (int id); @@ -112,6 +113,7 @@ namespace torrents void Accept (); void ReadTorrentFile (const std::filesystem::path& torrentFilePath); + void SaveTorrentFile (std::shared_ptr torrent); void InitTorrentFiles (std::shared_ptr torrent); int InsertTorrent (std::shared_ptr torrent); // returns id > 0 if success and 0 if failed void RemoveTorrent (std::shared_ptr torrent, bool deleteFiles); From aad34ec2426aa745d35bf4a87d57ce3ac487467a Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Sep 2026 20:42:45 -0400 Subject: [PATCH 37/38] ignore total_size from BEP9 metadata. Check size of magnet hash --- libi2pd_client/Torrents.cpp | 19 ++++++++----------- libi2pd_client/TorrentsTunnel.cpp | 4 ++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 4c6b7aa2..5f212a3d 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -1693,9 +1693,8 @@ namespace torrents { if (!m_Torrent) return; int msgType = -1, piece = -1; - size_t size = 0; auto payloadLen = ParseDictionary (std::string_view ((const char *)buf, len), - [&msgType, &piece, &size](std::string_view key, std::string_view buf)->size_t + [&msgType, &piece](std::string_view key, std::string_view buf)->size_t { if (key == "msg_type") { @@ -1709,12 +1708,7 @@ namespace torrents if (l) piece = value; return l; } - else if (key == "total_size") - { - auto [s, l] = ExtractInteger (buf); - if (l) size = s; - return l; - } + // ignore total_szie return 0; }); if (msgType >=0 && piece >= 0) @@ -1731,7 +1725,7 @@ namespace torrents SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (1) }, { "piece", CreateInteger (piece) }, - { "total_size", CreateInteger (totalSize) } }), + { "total_size", CreateInteger (m_Torrent->GetInfo ().size ()) } }), std::string_view ((const char *)info.data () + offset, totalSize)); } else @@ -1740,12 +1734,15 @@ namespace torrents } case 1: // data { - if (payloadLen + size > len) break; size_t offset = piece*REQUEST_BLOCK_SIZE; + if (offset > m_RemoteMetadataSize) break; + size_t size = m_RemoteMetadataSize - offset; + if (size > REQUEST_BLOCK_SIZE) size = REQUEST_BLOCK_SIZE; + if (payloadLen + size > len) break; if (offset == m_RemoteMetadata.size () && size) { m_RemoteMetadata.resize (m_RemoteMetadata.size () + size); - memcpy (m_RemoteMetadata.data () + offset,buf + payloadLen, size); + memcpy (m_RemoteMetadata.data () + offset, buf + payloadLen, size); if (m_RemoteMetadata.size () < m_RemoteMetadataSize) // request next piece SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (0) }, { "piece", CreateInteger (piece + 1) }})); diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index fbcca43e..1c106e8d 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -367,7 +367,7 @@ namespace torrents std::string_view param; if (pos != std::string::npos) { - param = magnet.substr (0, pos - 1); + param = magnet.substr (0, pos); magnet = magnet.substr (pos + 1); } else @@ -382,7 +382,7 @@ namespace torrents if (param.substr (0, hashPrefix.size ()) == hashPrefix) #endif { - std::string_view hexStr = param.substr (hashPrefix.size ()); + std::string_view hexStr = param.substr (hashPrefix.size (), infoHash.size ()*2); try { boost::algorithm::unhex (hexStr.begin(), hexStr.end(), infoHash.begin()); From b5989bec8a86ef59f63af672f5a372019f1ebc5c Mon Sep 17 00:00:00 2001 From: orignal Date: Fri, 4 Sep 2026 20:50:52 -0400 Subject: [PATCH 38/38] fixed typo --- libi2pd_client/Torrents.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 5f212a3d..5d580504 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -1752,7 +1752,7 @@ namespace torrents LogPrint (eLogDebug, "Torrents: ut_metadata ", m_RemoteMetadataSize, " bytes of info received"); uint8_t digest[SHA_DIGEST_LENGTH]; SHA1 (m_RemoteMetadata.data (), m_RemoteMetadata.size (), digest); - if (memcmp (m_Torrent->GetInfoHash ().data (), digest, SHA_DIGEST_LENGTH)) + if (!memcmp (m_Torrent->GetInfoHash ().data (), digest, SHA_DIGEST_LENGTH)) GetTorrentsTunnel ()->UpdateTorrentInfo (m_Torrent, std::string_view ((const char *)m_RemoteMetadata.data (), m_RemoteMetadata.size ())); else LogPrint (eLogError, "Torrents: ut_metadata info doesn't match infoHash");