Working proof of concept

This commit is contained in:
King_DuckZ 2025-04-02 20:41:48 +01:00
commit 3dcfcc7f0c
6 changed files with 394 additions and 0 deletions

17
meson.build Normal file
View file

@ -0,0 +1,17 @@
project('ducktorrent', 'cpp',
version: '0.1.0',
meson_version: '>=0.56.0',
default_options: [
'buildtype=release',
'cpp_std=gnu++20',
'b_ndebug=if-release',
'c_std=c99',
],
)
boost_dep = dependency('boost', version: '>=1.75.0')
libstriezel_proj = subproject('libstriezel')
libstriezel_dep = libstriezel_proj.get_variable('libstriezel_dep')
subdir('src')

213
src/main.cpp Normal file
View file

@ -0,0 +1,213 @@
#include "parser.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <cctype>
#include <algorithm>
#include <ciso646>
#include <cstdint>
#include <array>
#include <stdexcept>
#include <boost/variant/apply_visitor.hpp>
#include <sha1/sha1.hpp>
#include <sha1/FileSource.hpp>
namespace {
constexpr char g_test[] = "d8:announce75:http://tracker.bakabt.me:2710/f53895da71a9546b3b14682e63cb9069/announce.php7:comment21:http://www.bakabt.me/10:created by37:ruTorrent (PHP Class - Adrien Gibrat)13:creation datei1394860194ee";
std::string load_file (const std::string& path) {
std::ifstream ifs(path);
ifs >> std::noskipws;
return std::string(
std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>()
);
}
bool is_printable(std::string_view s) {
return std::find_if(s.cbegin(), s.cend(),
[](unsigned char c){ return not std::isprint(c); }
) == s.cend();
}
struct PrintVisitor {
PrintVisitor(int depth=0, bool skip_first=true) :
depth(depth), first_line(skip_first), skip_first(skip_first) {}
void print_tabs(int extra=0) {
if (!first_line) {
for (int z = 0; z < depth+extra; ++z) std::cout << '\t';
}
else {
first_line = false;
}
}
void reset() {
first_line = skip_first;
}
void operator() (long long value) {
print_tabs();
std::cout << value << '\n';
}
void operator() (const duck::TorrentStringType& value) {
constexpr std::size_t max_size = 41;
print_tabs();
std::string_view str{value};
std::string_view ellip;
if (is_printable(value)) {
if (value.size() > max_size + 3) {
str = str.substr(0, max_size);
ellip = "...";
}
}
else {
str = "<binary_data>";
}
std::cout << "\"" << str << ellip << "\"[" << value.size() << "]\n";
}
void operator() (const std::vector<duck::TorrentValue>& value) {
print_tabs();
std::cout << "[\n";
PrintVisitor new_visitor(depth+1, false);
for (const auto& item : value) {
boost::apply_visitor(new_visitor, item);
}
print_tabs();
std::cout << "]\n";
}
void operator() (const std::map<duck::TorrentStringType, duck::TorrentValue>& value) {
print_tabs();
std::cout << "{\n";
PrintVisitor new_visitor(depth+1);
for (const auto& item : value) {
print_tabs(1);
std::cout << item.first << " => ";
new_visitor.reset();
boost::apply_visitor(new_visitor, item.second);
}
print_tabs();
std::cout << "}\n";
}
int depth;
bool first_line;
bool skip_first;
};
struct FindHashesVisitor : boost::static_visitor<std::string_view> {
std::string_view operator() (long long value) {
return {};
}
std::string_view operator() (const duck::TorrentStringType& value) {
if (this_is_pieces)
return value;
else
return {};
}
std::string_view operator() (const std::vector<duck::TorrentValue>& value) {
return {};
}
std::string_view operator() (const std::map<duck::TorrentStringType, duck::TorrentValue>& value) {
if (this_is_pieces)
return {};
for (const auto& item : value) {
if (item.first == "info") {
return boost::apply_visitor(*this, item.second);
}
else if (item.first == "pieces") {
this_is_pieces = true;
return boost::apply_visitor(*this, item.second);
}
}
return {};
}
bool this_is_pieces{false};
};
std::vector<std::array<std::uint32_t, 5>> collect_hashes (std::string_view hashes) {
typedef std::array<std::uint32_t, 5> array_type;
if (hashes.empty())
return {};
constexpr std::size_t hash_size = 20;
constexpr std::size_t uint_count = std::tuple_size<array_type>();
static_assert(sizeof(array_type::value_type) * uint_count == hash_size);
if (hashes.size() % hash_size != 0)
throw std::runtime_error("Bad pieces array size " + std::to_string(hashes.size()));
std::vector<array_type> retval(hashes.size() / hash_size);
const char* src = hashes.data();
for (std::size_t z = 0; z < retval.size(); ++z, src+=hash_size) {
//std::copy_n(src, hash_size, reinterpret_cast<char*>(retval[z].data()));
for (std::size_t n = 0; n < uint_count; ++n) {
char* const out_uint = reinterpret_cast<char*>(retval[z].data() + n);
for (std::size_t u = 0; u < sizeof(std::uint32_t); ++u) {
out_uint[u] = src[n * sizeof(std::uint32_t) + sizeof(std::uint32_t) - u - 1];
}
}
}
return retval;
}
} //unnamed namespace
int main(int argc, const char* argv[]) {
using duck::parse_torrent;
if (argc != 2) {
std::cerr << "Wrong number of parameters. Usage:\n"
<< argv[0] << " <path_to_torrent>\n";
return 2;
}
std::string full_torrent = load_file(argv[1]);
std::cout << "Loaded file into string of size " << full_torrent.size() << '\n';
std::vector<duck::TorrentValue> values = parse_torrent(full_torrent);
for (const auto& value : values) {
PrintVisitor visitor;
boost::apply_visitor(visitor, value);
}
if (values.size() == 1) {
FindHashesVisitor visitor;
auto source_hashes = boost::apply_visitor(visitor, values.front());
std::cout << "Got source_hashes with size " << source_hashes.size() << '\n';
auto hashes = collect_hashes(source_hashes);
std::cout << "Got " << hashes.size() << " hashes\n";
if (not hashes.empty()) {
SHA1::MessageDigest hash_found;
std::copy_n(hashes.front().begin(), 5, hash_found.hash);
std::cout << hash_found.toHexString() << '\n';
}
}
{
SHA1::MessageDigest hash160;
SHA1::FileSource file_source;
const char* const hash_path = argv[1];
file_source.open(hash_path);
hash160 = SHA1::computeFromSource(file_source);
std::cout << hash160.toHexString() << " " << hash_path << '\n';
}
}

9
src/meson.build Normal file
View file

@ -0,0 +1,9 @@
executable(meson.project_name(),
'main.cpp',
'parser.cpp',
dependencies: [
boost_dep,
libstriezel_dep,
],
install: true,
)

131
src/parser.cpp Normal file
View file

@ -0,0 +1,131 @@
#if !defined(NDEBUG)
//# define BOOST_SPIRIT_X3_DEBUG
#endif
#include "parser.hpp"
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#if !defined(NDEBUG)
# include <iostream>
#endif
namespace boost::spirit::x3::traits {
template <typename Char, typename Trait>
struct is_range<std::basic_string_view<Char, Trait>> : boost::mpl::true_ {};
} //namespace boost::spirit::x3::traits
namespace duck {
namespace x3 = boost::spirit::x3;
#if defined(BOOST_SPIRIT_X3_DEBUG)
std::ostream& operator<<(std::ostream& os, const std::vector<TorrentValue>& ot) {
os << "<list-" << ot.size() << '>';
return os;
}
std::ostream& operator<<(std::ostream& os, const std::map<TorrentStringType, TorrentValue>& ot) {
os << "<map-" << ot.size() << '>';
return os;
}
#endif
namespace workaround {
template <typename Subject>
struct raw_directive : x3::raw_directive<Subject> {
using x3::raw_directive<Subject>::raw_directive;
template <typename Iterator, typename Context, typename RContext, typename Attribute>
bool parse(
Iterator& first,
Iterator const& last,
Context const& context,
RContext& rcontext,
Attribute& attr
) const {
x3::skip_over(first, last, context);
Iterator saved = first;
if (this->subject.parse(first, last, context, rcontext, x3::unused)) {
attr = { saved, typename Attribute::size_type(first - saved) };
return true;
}
return false;
}
};
struct raw_gen {
template <typename Subject>
raw_directive<typename x3::extension::as_parser<Subject>::value_type>
operator[](Subject subject) const {
return { x3::as_parser(std::move(subject)) };
}
};
auto const raw = raw_gen{};
template <typename T>
struct as_type {
template <typename E>
constexpr auto operator[](E e) const { return x3::rule<struct _, T> {} = e; }
};
template <typename T>
static inline constexpr as_type<T> as;
} //namespace workaround
namespace {
namespace parser {
using x3::lit;
using x3::char_;
using x3::uint_;
using x3::string;
using x3::eps;
using x3::omit;
using workaround::raw;
using workaround::as;
//using x3::ascii::digit;
class len_tag; //see https://github.com/boostorg/spirit/issues/680
const x3::rule<class TorrentID, std::vector<TorrentValue>> torrent = "torrent";
const x3::rule<class TorrentValueID, TorrentValue> torrent_value = "torrent_value";
const x3::rule<class TorrentIntID, int> torrent_int = "torrent_int";
const x3::rule<class TorrentStringID, TorrentStringType, true> torrent_string = "torrent_string";
const x3::rule<class TorrentListID, std::vector<TorrentValue>> torrent_list = "torrent_list";
const x3::rule<class TorrentDictID, std::map<TorrentStringType, TorrentValue>> torrent_dict = "torrent_dict";
auto torrent_value_def = torrent_int | torrent_string | torrent_list | torrent_dict;
//auto torrent_int_def = lit('i') >> (string("0") | (char_("1-9") >> *digit)) >> lit('e');
auto torrent_int_def = lit('i') >> x3::long_long >> lit('e');
auto torrent_string_def = x3::with<len_tag>(0u)[
omit[uint_[([](auto& ctx) { x3::get<len_tag>(ctx) = x3::_attr(ctx); })] >> ':'] >> raw[*(
eps[([](auto& ctx) { x3::_pass(ctx) = x3::get<len_tag>(ctx)-- > 0; })] >> as<TorrentStringType>[char_]
)]
];
auto torrent_list_def = lit('l') >> *torrent_value >> 'e';
auto torrent_dict_def = lit('d') >> *(torrent_string >> torrent_value) >> lit('e');
auto torrent_def = +torrent_value;
BOOST_SPIRIT_DEFINE(torrent_int, torrent_string, torrent_list, torrent_dict, torrent_value, torrent);
} //namespace parser
} //unnamed namespace
std::vector<TorrentValue> parse_torrent (std::string_view binary_data) {
using boost::spirit::x3::parse;
const char* begin = binary_data.data();
const char* const end = binary_data.data() + binary_data.size();
std::vector<TorrentValue> retval;
const bool r = parse(begin, end, parser::torrent, retval);
#if !defined(NDEBUG)
std::cout << "done, r=" << r << " (" << begin-binary_data.data() << ")\n";
#endif
return retval;
}
} //namespace duck

23
src/parser.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <string_view>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <map>
#include <vector>
namespace duck {
typedef std::string_view TorrentStringType;
struct TorrentValue : public boost::spirit::x3::variant<
long long,
TorrentStringType,
boost::spirit::x3::forward_ast< std::vector<TorrentValue> >,
boost::spirit::x3::forward_ast< std::map<TorrentStringType, TorrentValue> >
> {
using base_type::base_type;
using base_type::operator=;
};
std::vector<TorrentValue> parse_torrent (std::string_view binary_data);
} //namespace duck

@ -0,0 +1 @@
Subproject commit 12869dd16fbad4bc60eb264be13cf9f9b6b089ba