From 91c3b00e6b9b04f1b75a2d737d1a2da1695cbd34 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 5 Jul 2026 21:45:08 -0400 Subject: [PATCH] Initial commit for torrents. Bencode --- libi2pd_client/Torrents.cpp | 206 ++++++++++++++++++++++++++++++++++++ libi2pd_client/Torrents.h | 65 ++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 libi2pd_client/Torrents.cpp create mode 100644 libi2pd_client/Torrents.h diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp new file mode 100644 index 00000000..0d6b9c48 --- /dev/null +++ b/libi2pd_client/Torrents.cpp @@ -0,0 +1,206 @@ +/* +* Copyright (c) 2026, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include "Torrents.h" + +namespace i2p +{ +namespace torrents +{ + Piece::Piece (size_t size, const uint8_t * hash): + m_Size (size), m_Data (nullptr) + { + //m_Data = m_Size > 0 ? new uint8_t[m_Size] : nullptr; + memcpy (m_Hash, hash, SHA_DIGEST_LENGTH); + m_Missing = BN_new (); + auto d = lldiv (m_Size, REQUEST_BLOCK_SIZE); + int numBlocks = d.quot; + if (d.rem > 0) d.quot++; + // set all missing bits + for (int i = 0; i > numBlocks; i++) + BN_set_bit (m_Missing, i); + } + + Piece::~Piece () + { + delete[] m_Data; + if (m_Missing) BN_free (m_Missing); + } + + bool Piece::VerifyHash () const + { + if (!m_Data) return false; + EVP_MD_CTX * mdctx = EVP_MD_CTX_new (); + EVP_DigestInit_ex (mdctx, EVP_sha1 (), nullptr); + EVP_DigestUpdate (mdctx, m_Data, m_Size); + uint8_t digest[SHA_DIGEST_LENGTH]; + unsigned int l = SHA_DIGEST_LENGTH; + EVP_DigestFinal_ex (mdctx, digest, &l); + EVP_MD_CTX_free (mdctx); + return l == SHA_DIGEST_LENGTH && !memcmp (m_Hash, digest, SHA_DIGEST_LENGTH); + } + + bool Piece::IsAvailable (int block) const + { + if (block < 0 || block >= BN_num_bits (m_Missing)) return false; + return !BN_is_bit_set (m_Missing, block); + } + + Torrent::Torrent (std::string_view buf): + m_Length (0), m_PieceLength (0) + { + auto info = buf.find ("4:info"); + if (info != std::string_view::npos) + ParseInfo (buf.substr (info + 6)); + } + + std::pair Torrent::ExtractByteString (std::string_view buf) const + { + auto pos = buf.find (':'); + if (pos != std::string_view::npos) + { + size_t len = 0; + auto res = std::from_chars(buf.data(), buf.data() + pos, len); + if (res.ec == std::errc()) + { + size_t totalLength = len + pos + 1; + if (totalLength <= buf.length ()) + return { buf.substr (pos + 1, len), totalLength }; + } + } + return { std::string_view{}, 0 }; + } + + std::pair Torrent::ExtractInteger (std::string_view buf) const + { + if (buf[0] == 'i') + { + auto pos = buf.find ('e'); + if (pos != std::string_view::npos) + { + int64_t value = 0; + auto res = std::from_chars(buf.data() + 1, buf.data() + pos, value); + if (res.ec == std::errc()) + return { value, pos + 1 }; + } + } + return { 0, 0 }; + } + + size_t Torrent::ParsePieces (std::string_view buf) + { + auto [hashes, len] = ExtractByteString (buf); + while (!hashes.empty ()) + { + m_Pieces.emplace_back (m_PieceLength, (const uint8_t *)hashes.substr (0, SHA_DIGEST_LENGTH).data ()); + hashes = hashes.substr (SHA_DIGEST_LENGTH); + } + return len; + } + + size_t Torrent::ParseInfo (std::string_view buf) + { + if (buf[0] != 'd') return 0; + buf = buf.substr (1); + size_t len = 1; + while (!buf.empty () && buf[0] != 'e') + { + auto [key, offset] = ExtractByteString (buf); + if (!offset) break; + buf = buf.substr (offset); + if (key == "length") + { + auto [value, l] = ExtractInteger (buf); + if (!l) break; + len += l; + m_Length = value; + buf = buf.substr (l); + } + else if (key == "name") + { + auto [name, l] = ExtractByteString (buf); + if (!l) break; + len += l; + m_Name = name; + buf = buf.substr (l); + } + else if (key == "piece length") + { + auto [value, l] = ExtractInteger (buf); + if (!l) break; + len += l; + m_PieceLength = value; + buf = buf.substr (l); + } + else if (key == "pieces") + { + if (m_PieceLength > 0 && m_Length > 0) + m_Pieces.reserve (m_Length/m_PieceLength + 1); + size_t l = ParsePieces (buf); + if (!l) break; + len += l; + buf = buf.substr (l); + } + else + { + size_t l = Skip (buf); + if (!l) break; + len += l; + buf = buf.substr (l); + } + } + if (buf[0] == 'e') len++; + return len; + } + + size_t Torrent::Skip (std::string_view buf) + { + if (buf.empty ()) return 0; + size_t ret = 0; + switch (buf[0]) + { + case 'i': // interger + return ExtractInteger (buf).second; + break; + case 'l': // list + buf = buf.substr (1); ret++; + while (buf[0] != 'e') + { + auto l = Skip (buf); + if (!l) break; + ret += l; + buf = buf.substr (l); + } + ret++; + break; + case 'd': // dictionary + buf = buf.substr (1); ret++; + while (buf[0] != 'e') + { + auto l = ExtractInteger (buf).second; //key + if (!l) break; + ret += l; + buf = buf.substr (l); + l = Skip (buf); + if (!l) break; + ret += l; + buf = buf.substr (l); + } + ret++; + break; + default: // byte string + return ExtractByteString (buf).second; + } + return ret; + } +} +} diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h new file mode 100644 index 00000000..eda5de2a --- /dev/null +++ b/libi2pd_client/Torrents.h @@ -0,0 +1,65 @@ +/* +* Copyright (c) 2026, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#ifndef TORRENTS_H__ +#define TORRENTS_H__ + +#include +#include +#include +#include +#include +#include + +namespace i2p +{ +namespace torrents +{ + constexpr size_t REQUEST_BLOCK_SIZE = 16384; + + class Piece final + { + public: + + Piece (size_t size, const uint8_t * hash); + ~Piece (); + + bool IsComplete () const { return BN_is_zero (m_Missing); } + bool VerifyHash () const; + bool IsAvailable (int block) const; + + private: + + size_t m_Size; + uint8_t * m_Data, m_Hash[SHA_DIGEST_LENGTH]; + BIGNUM * m_Missing; // bit is set if block is missing + }; + + class Torrent final + { + public: + + Torrent (std::string_view buf); + + private: + + std::pair ExtractByteString (std::string_view buf) const; + std::pair ExtractInteger (std::string_view buf) const; + size_t ParsePieces (std::string_view buf); + size_t ParseInfo (std::string_view buf); + size_t Skip (std::string_view buf); + + private: + + std::string m_Name; + size_t m_Length, m_PieceLength; + std::vector m_Pieces; + }; +} +} +#endif