/* Copyright (c) 2006-2022, Arvid Norberg Copyright (c) 2015-2017, 2020-2021, Alden Torres Copyright (c) 2016, Andrei Kurushin Copyright (c) 2016-2018, Steven Siloti Copyright (c) 2017, 2020, AllSeeingEyeTolledEweSew Copyright (c) 2018, d-komarov Copyright (c) 2020, Paul-Louis Ageneau All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #include #include #include #include "libtorrent/session.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/aux_/http_parser.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/create_torrent.hpp" #include "libtorrent/aux_/socket_io.hpp" // print_endpoint #include "libtorrent/ip_filter.hpp" #include "libtorrent/session_stats.hpp" #include "libtorrent/aux_/random.hpp" #include "libtorrent/torrent_info.hpp" #include "libtorrent/hex.hpp" // to_hex #include "libtorrent/aux_/vector.hpp" #include "libtorrent/aux_/path.hpp" #include "libtorrent/aux_/merkle.hpp" #include "libtorrent/disk_interface.hpp" // for default_block_size #include "libtorrent/aux_/ip_helpers.hpp" #include "libtorrent/load_torrent.hpp" #include "test.hpp" #include "test_utils.hpp" #include "setup_transfer.hpp" #ifndef _WIN32 #include #include #endif using namespace lt; #if defined TORRENT_WINDOWS #include #endif #if defined TORRENT_WINDOWS #define SEPARATOR "\\" #else #define SEPARATOR "/" #endif lt::add_torrent_params generate_torrent(bool const with_files, bool const with_hashes) { if (with_files) { error_code ec; create_directories("test_resume", ec); std::vector a(128 * 1024 * 8); std::vector b(128 * 1024); ofstream("test_resume/tmp1").write(a.data(), std::streamsize(a.size())); ofstream("test_resume/tmp2").write(b.data(), std::streamsize(b.size())); ofstream("test_resume/tmp3").write(b.data(), std::streamsize(b.size())); } std::vector fs; fs.emplace_back("test_resume/tmp1", 128 * 1024 * 8); fs.emplace_back("test_resume/tmp2", 128 * 1024); fs.emplace_back("test_resume/tmp3", 128 * 1024); lt::create_torrent t(std::move(fs), 128 * 1024); t.set_comment("test comment"); t.set_creator("libtorrent test"); t.add_tracker("http://torrent-file-tracker.com/announce"); t.add_url_seed("http://torrent-file-url-seed.com/"); TEST_CHECK(t.num_pieces() > 0); if (with_hashes) { lt::set_piece_hashes(t, "." , [] (lt::piece_index_t) {}); } else { for (auto const i : t.piece_range()) { sha1_hash ph; aux::random_bytes(ph); t.set_hash(i, ph); } for (file_index_t f : t.file_range()) { for (auto const i : t.file_piece_range(f)) { sha256_hash ph; aux::random_bytes(ph); t.set_hash2(f, i, ph); } } } return load_torrent_buffer(bencode(t.generate())); } namespace { std::uint32_t g_addr = 0x92343023; address_v6::bytes_type g_addr6 = {0x93, 0x30, 0x2e, 0xf4, 0x1c, 0x01, 0x3d, 0x8a , 0x35, 0x3d, 0x69, 0x10, 0x55, 0x82, 0x9d, 0x2f}; } void init_rand_address() { g_addr = 0x92343023; g_addr6 = address_v6::bytes_type{ {0x93, 0x30, 0x2e, 0xf4, 0x1c, 0x01, 0x3d, 0x8a , 0x35, 0x3d, 0x69, 0x10, 0x55, 0x82, 0x9d, 0x2f}}; } address rand_v4() { address_v4 ret; do { g_addr += 0x3080ca; ret = address_v4(g_addr); } while (ret.is_unspecified() || aux::is_local(ret) || ret.is_loopback()); return ret; } sha1_hash rand_hash() { sha1_hash ret; aux::random_bytes(ret); return ret; } sha1_hash to_hash(char const* s) { sha1_hash ret; aux::from_hex({s, 40}, ret.data()); return ret; } namespace { void add_mp(span target, span add) { TORRENT_ASSERT(target.size() == add.size()); int carry = 0; for (int i = int(target.size()) - 1; i >= 0; --i) { int const res = carry + int(target[i]) + add[i]; carry = res >> 8; target[i] = std::uint8_t(res & 255); } } } address rand_v6() { static address_v6::bytes_type const add{ {0x93, 0x30, 0x2e, 0xf4, 0x1c, 0x01, 0x3d, 0x8a , 0x35, 0x3d, 0x69, 0x10, 0x55, 0x82, 0x9d, 0x23}}; address_v6 ret; do { add_mp(g_addr6, add); ret = address_v6(g_addr6); } while (ret.is_unspecified() || aux::is_local(ret) || ret.is_loopback()); return ret; } static std::uint16_t g_port = 0; tcp::endpoint rand_tcp_ep(lt::address(&rand_addr)()) { // make sure we don't produce the same "random" port twice g_port = (g_port + 1) % 14038; return tcp::endpoint(rand_addr(), g_port + 1024); } udp::endpoint rand_udp_ep(lt::address(&rand_addr)()) { g_port = (g_port + 1) % 14037; return udp::endpoint(rand_addr(), g_port + 1024); } bool supports_ipv6() { #if defined TORRENT_BUILD_SIMULATOR return true; #elif defined TORRENT_WINDOWS try { error_code ec; make_address("::1", ec); return !ec; } catch (std::exception const&) { return false; } #else io_context ios; tcp::socket test(ios); error_code ec; test.open(tcp::v6(), ec); if (ec) return false; error_code ignore; test.bind(tcp::endpoint(make_address_v6("::1", ignore), 0), ec); return !bool(ec); #endif } std::map get_counters(lt::session& s) { using namespace lt; s.post_session_stats(); std::map ret; alert const* a = wait_for_alert(s, session_stats_alert::alert_type , "get_counters()"); TEST_CHECK(a); if (!a) return ret; session_stats_alert const* sa = alert_cast(a); if (!sa) return ret; static std::vector metrics = session_stats_metrics(); for (auto const& m : metrics) ret[m.name] = sa->counters()[m.value_index]; return ret; } namespace { bool should_print(lt::alert* a) { #ifndef TORRENT_DISABLE_LOGGING if (auto pla = alert_cast(a)) { if (pla->direction != peer_log_alert::incoming_message && pla->direction != peer_log_alert::outgoing_message && pla->direction != peer_log_alert::info) return false; } #endif if (alert_cast(a) || alert_cast(a) || alert_cast(a) || alert_cast(a)) { return false; } return true; } } alert const* wait_for_alert(lt::session& ses, int type, char const* name , pop_alerts const p, lt::time_duration timeout) { // we pop alerts in batches, but we wait for individual messages. This is a // cache to keep around alerts that came *after* the one we're waiting for. // To let subsequent calls to this function be able to pick those up, despite // already being popped off the sessions alert queue. static std::map> cache; auto& alerts = cache[&ses]; time_point const end_time = lt::clock_type::now() + timeout; while (true) { time_point const now = clock_type::now(); if (now > end_time) return nullptr; alert const* ret = nullptr; if (alerts.empty()) { ses.wait_for_alert(end_time - now); ses.pop_alerts(&alerts); } for (auto i = alerts.begin(); i != alerts.end(); ++i) { auto a = *i; if (should_print(a)) { std::printf("%s: %s: [%s] %s\n", time_now_string().c_str(), name , a->what(), a->message().c_str()); } if (a->type() == type) { ret = a; if (p == pop_alerts::pop_all) alerts.clear(); else alerts.erase(alerts.begin(), std::next(i)); return ret; } } alerts.clear(); } } int load_file(std::string const& filename, std::vector& v , lt::error_code& ec, int limit) { ec.clear(); FILE* f = fopen(filename.c_str(), "rb"); if (f == nullptr) { ec.assign(errno, boost::system::system_category()); return -1; } int r = fseek(f, 0, SEEK_END); if (r != 0) { ec.assign(errno, boost::system::system_category()); fclose(f); return -1; } long s = ftell(f); if (s < 0) { ec.assign(errno, boost::system::system_category()); fclose(f); return -1; } if (s > limit) { fclose(f); return -2; } r = fseek(f, 0, SEEK_SET); if (r != 0) { ec.assign(errno, boost::system::system_category()); fclose(f); return -1; } v.resize(static_cast(s)); if (s == 0) { fclose(f); return 0; } r = int(fread(&v[0], 1, v.size(), f)); if (r < 0) { ec.assign(errno, boost::system::system_category()); fclose(f); return -1; } fclose(f); if (r != s) return -3; return 0; } namespace { std::string print_endpoint(peer_endpoint_t const& ep) { if (auto i = std::get_if(&ep)) { return print_endpoint(*i); } else if (auto i2p = std::get_if(&ep)) { return lt::aux::to_hex(*i2p); } TORRENT_ASSERT_FAIL(); return {}; } } bool print_alerts(lt::session& ses, char const* name , bool allow_no_torrents, bool allow_failed_fastresume , std::function predicate, bool no_output) { TEST_CHECK(!ses.get_torrents().empty() || allow_no_torrents); std::vector alerts; ses.pop_alerts(&alerts); for (auto a : alerts) { if (peer_disconnected_alert const* p = alert_cast(a)) { std::printf("%s: %s: [%s] (%s): %s\n", time_to_string(a->timestamp()).c_str() , name, a->what() , print_endpoint(p->ep).c_str(), p->message().c_str()); } else if (a->type() == invalid_request_alert::alert_type) { fprintf(stdout, "peer error: %s\n", a->message().c_str()); TEST_CHECK(false); } else if (a->type() == fastresume_rejected_alert::alert_type) { fprintf(stdout, "resume data error: %s\n", a->message().c_str()); TEST_CHECK(allow_failed_fastresume); } else if (should_print(a) && !no_output) { std::printf("%s: %s: [%s] %s\n", time_now_string().c_str(), name, a->what(), a->message().c_str()); } TEST_CHECK(alert_cast(a) == nullptr || allow_failed_fastresume); invalid_request_alert const* ira = alert_cast(a); if (ira) { std::printf("peer error: %s\n", ira->message().c_str()); TEST_CHECK(false); } } return predicate && std::any_of(alerts.begin(), alerts.end(), predicate); } void wait_for_listen(lt::session& ses, char const* name) { bool listen_done = false; do { listen_done = print_alerts(ses, name, true, true, [](lt::alert const* al) { return alert_cast(al) || alert_cast(al); } , false); if (listen_done) break; } while (ses.wait_for_alert(milliseconds(500))); // we din't receive a listen alert! TEST_CHECK(listen_done); } void wait_for_downloading(lt::session& ses, char const* name) { time_point start = clock_type::now(); bool downloading_done = false; do { downloading_done = print_alerts(ses, name, true, true , [](lt::alert const* al) { state_changed_alert const* sc = alert_cast(al); return sc && sc->state == torrent_status::downloading; }, false); if (downloading_done) break; if (clock_type::now() - start > seconds(30)) break; } while (ses.wait_for_alert(seconds(5))); if (!downloading_done) { std::printf("%s: did not receive a state_changed_alert indicating " "the torrent is downloading. waited: %d ms\n" , name, int(total_milliseconds(clock_type::now() - start))); } } void wait_for_seeding(lt::session& ses, char const* name) { time_point start = clock_type::now(); bool seeding = false; do { seeding = print_alerts(ses, name, true, true , [](lt::alert const* al) { state_changed_alert const* sc = alert_cast(al); return sc && sc->state == torrent_status::seeding; }, false); if (seeding) break; if (clock_type::now() - start > seconds(30)) break; } while (ses.wait_for_alert(seconds(5))); if (!seeding) { std::printf("%s: did not receive a state_changed_alert indicating " "the torrent is seeding. waited: %d ms\n" , name, int(total_milliseconds(clock_type::now() - start))); } } void print_ses_rate(lt::clock_type::time_point const start_time , lt::torrent_status const* st1 , lt::torrent_status const* st2 , lt::torrent_status const* st3) { auto const d = lt::clock_type::now() - start_time; std::printf("%d.%03ds " , int(duration_cast(d).count()) , int(duration_cast(d).count() % 1000)); if (st1) { std::printf("| %dkB/s %dkB/s %d%% %d cc:%d%s" , int(st1->download_payload_rate / 1000) , int(st1->upload_payload_rate / 1000) , int(st1->progress * 100) , st1->num_peers , st1->connect_candidates , st1->errc ? (" [" + st1->errc.message() + "]").c_str() : ""); } if (st2) std::printf(" | %dkB/s %dkB/s %d%% %d cc:%d%s" , int(st2->download_payload_rate / 1000) , int(st2->upload_payload_rate / 1000) , int(st2->progress * 100) , st2->num_peers , st2->connect_candidates , st2->errc ? (" [" + st1->errc.message() + "]").c_str() : ""); if (st3) std::printf(" | %dkB/s %dkB/s %d%% %d cc:%d%s" , int(st3->download_payload_rate / 1000) , int(st3->upload_payload_rate / 1000) , int(st3->progress * 100) , st3->num_peers , st3->connect_candidates , st3->errc ? (" [" + st1->errc.message() + "]").c_str() : ""); std::printf("\n"); } #ifdef _WIN32 using pid_type = DWORD; #else using pid_type = pid_t; #endif namespace { // returns 0 on failure, otherwise pid pid_type async_run(char const* cmdline) { #ifdef _WIN32 char buf[2048]; std::snprintf(buf, sizeof(buf), "%s", cmdline); std::printf("CreateProcess %s\n", buf); PROCESS_INFORMATION pi; STARTUPINFOA startup{}; startup.cb = sizeof(startup); startup.dwFlags = STARTF_USESTDHANDLES; startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup.hStdError = GetStdHandle(STD_OUTPUT_HANDLE); int const ret = CreateProcessA(nullptr, buf, nullptr, nullptr, TRUE , 0, nullptr, nullptr, &startup, &pi); if (ret == 0) { int const error = GetLastError(); std::printf("ERROR: (%d) %s\n", error, error_code(error, system_category()).message().c_str()); return 0; } DWORD len = sizeof(buf); if (QueryFullProcessImageNameA(pi.hProcess, PROCESS_NAME_NATIVE, buf, &len) == 0) { int const error = GetLastError(); std::printf("ERROR: QueryFullProcessImageName (%d) %s\n", error , error_code(error, system_category()).message().c_str()); } else { std::printf("launched: %s\n", buf); } return pi.dwProcessId; #else pid_type p; char arg_storage[4096]; char* argp = arg_storage; std::vector argv; argv.push_back(argp); for (char const* in = cmdline; *in != '\0'; ++in) { if (*in != ' ') { *argp++ = *in; continue; } *argp++ = '\0'; argv.push_back(argp); } *argp = '\0'; argv.push_back(nullptr); int ret = posix_spawnp(&p, argv[0], nullptr, nullptr, &argv[0], nullptr); if (ret != 0) { std::printf("ERROR (%d) %s\n", errno, strerror(errno)); return 0; } return p; #endif } void stop_process(pid_type p) { #ifdef _WIN32 HANDLE proc = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, p); if (proc == nullptr) return; TerminateProcess(proc, 138); WaitForSingleObject(proc, 5000); CloseHandle(proc); #else std::printf("killing pid: %d\n", p); kill(p, SIGKILL); #endif } } // anonymous namespace struct proxy_t { pid_type pid; int type; }; // maps port to proxy type static std::map running_proxies; void stop_proxy(int port) { auto const it = running_proxies.find(port); if (it == running_proxies.end()) return; std::printf("stopping proxy on port %d\n", port); stop_process(it->second.pid); running_proxies.erase(it); } void stop_all_proxies() { std::map proxies = running_proxies; running_proxies.clear(); for (auto const& i : proxies) { stop_process(i.second.pid); } } namespace { #ifdef TORRENT_BUILD_SIMULATOR void wait_for_port(int) {} #else void wait_for_port(int const port) { // wait until the python program is ready to accept connections int i = 0; io_context ios; for (;;) { tcp::socket s(ios); error_code ec; s.open(tcp::v4(), ec); if (ec) { std::printf("ERROR opening probe socket: (%d) %s\n" , ec.value(), ec.message().c_str()); return; } s.connect(tcp::endpoint(make_address("127.0.0.1") , std::uint16_t(port)), ec); if (ec) { if (i == 100) { std::printf("ERROR: somehow the python program still hasn't " "opened its socket on port %d\n", port); return; } ++i; std::this_thread::sleep_for(lt::milliseconds(500)); continue; } if (ec) { std::printf("ERROR connecting probe socket: (%d) %s\n" , ec.value(), ec.message().c_str()); return; } return; } } #endif std::vector get_python() { std::vector ret; #ifdef _WIN32 char dummy[1]; DWORD const req_size = GetEnvironmentVariable("PYTHON_INTERPRETER", dummy, sizeof(dummy)); if (req_size > 1 && req_size < 4096) { std::vector buf(req_size); DWORD const sz = GetEnvironmentVariable("PYTHON_INTERPRETER", buf.data(), DWORD(buf.size())); if (size_t(sz) == buf.size() - 1) ret.emplace_back(buf.data(), buf.size()); } #endif ret.push_back("python3"); ret.push_back("python"); return ret; } int find_available_port() { int port = 2000 + (std::int64_t(::getpid()) + ::unit_test::g_test_idx + std::rand()) % 60000; error_code ec; io_context ios; // make sure the port we pick is free do { ++port; tcp::socket s(ios); s.open(tcp::v4(), ec); if (ec) break; s.bind(tcp::endpoint(make_address("127.0.0.1") , std::uint16_t(port)), ec); } while (ec); return port; } } // anonymous namespace // returns a port on success and -1 on failure int start_proxy(int proxy_type) { using namespace lt; std::map :: iterator i = running_proxies.begin(); for (; i != running_proxies.end(); ++i) { if (i->second.type == proxy_type) { return i->first; } } int const port = find_available_port(); char const* type = ""; char const* auth = ""; char const* cmd = ""; switch (proxy_type) { case settings_pack::socks4: type = "socks4"; auth = " --allow-v4"; cmd = ".." SEPARATOR "socks.py"; break; case settings_pack::socks5: type = "socks5"; cmd = ".." SEPARATOR "socks.py"; break; case settings_pack::socks5_pw: type = "socks5"; auth = " --username testuser --password testpass"; cmd = ".." SEPARATOR "socks.py"; break; case settings_pack::http: type = "http"; cmd = ".." SEPARATOR "http_proxy.py"; break; case settings_pack::http_pw: type = "http"; auth = " --basic-auth testuser:testpass"; cmd = ".." SEPARATOR "http_proxy.py"; break; } std::vector python_exes = get_python(); for (auto const& python_exe : python_exes) { char buf[1024]; std::snprintf(buf, sizeof(buf), "%s %s --port %d%s", python_exe.c_str(), cmd, port, auth); std::printf("%s starting proxy on port %d (%s %s)...\n", time_now_string().c_str(), port, type, auth); std::printf("%s\n", buf); pid_type r = async_run(buf); if (r == 0) continue; proxy_t t = { r, proxy_type }; running_proxies.insert(std::make_pair(port, t)); std::printf("%s launched\n", time_now_string().c_str()); std::this_thread::sleep_for(lt::milliseconds(500)); wait_for_port(port); return port; } abort(); } using namespace lt; std::vector generate_piece(piece_index_t const idx, int const piece_size) { using namespace lt; std::vector ret(static_cast(piece_size)); std::mt19937 rng(static_cast(static_cast(idx))); std::uniform_int_distribution rand(-128, 127); for (char& c : ret) { c = static_cast(rand(rng)); } return ret; } lt::file_storage make_file_storage(span const file_sizes , int const piece_size, std::string base_name) { using namespace lt; file_storage fs; for (std::ptrdiff_t i = 0; i != file_sizes.size(); ++i) { char filename[200]; std::snprintf(filename, sizeof(filename), "test%d", int(i)); char dirname[200]; std::snprintf(dirname, sizeof(dirname), "%s%d", base_name.c_str() , int(i) / 5); std::string full_path = combine_path(dirname, filename); fs.add_file(full_path, file_sizes[i]); } fs.set_piece_length(piece_size); fs.set_num_pieces(aux::calc_num_pieces(fs)); return fs; } add_torrent_params make_torrent(std::vector files , int piece_size, lt::create_flags_t const flags) { lt::create_torrent ct(std::move(files), piece_size, flags); piece_size = ct.piece_length(); aux::vector tree(merkle_num_nodes(piece_size / default_block_size)); std::int64_t file_offset = 0; std::int64_t const total_size = ct.total_size(); for (auto const f : ct.file_range()) { if (ct.file_at(f).flags & lt::file_storage::flag_pad_file) { file_offset += ct.file_at(f).size; continue; } lt::piece_index_t const first_piece(int(file_offset / piece_size)); std::int64_t piece_offset = static_cast(first_piece) * std::int64_t(piece_size); bool const aligned = piece_offset == file_offset; file_offset += ct.file_at(f).size; lt::piece_index_t const end_piece(int((file_offset + piece_size - 1) / piece_size)); for (auto piece = first_piece; piece < end_piece; ++piece, piece_offset += piece_size) { auto const this_piece_size = int(std::min(std::int64_t(piece_size), total_size - piece_offset)); auto const piece_size2 = int(std::min(std::int64_t(piece_size), file_offset - piece_offset)); auto const blocks_in_piece = (piece_size2 + lt::default_block_size - 1) / lt::default_block_size; std::vector piece_buf = generate_piece(piece, this_piece_size); if (aligned && piece_offset + this_piece_size > file_offset && ct.file_at(next(f)).flags & file_storage::flag_pad_file) { // this piece spans the next file. if it's a pad file, we need // to set that part to zeros int const pad_start = int(file_offset - piece_offset); std::size_t const pad_size = std::size_t(this_piece_size - pad_start); TORRENT_ASSERT(pad_start >= 0); TORRENT_ASSERT(pad_start < this_piece_size); TORRENT_ASSERT(pad_start < this_piece_size); std::memset(piece_buf.data() + pad_start, 0, pad_size); TORRENT_ASSERT(ct.file_at(next(f)).size + pad_start == this_piece_size); TORRENT_ASSERT(ct.file_at(next(f)).size == std::int64_t(pad_size)); } if (!(flags & lt::create_torrent::v1_only)) { for (int j = 0; j < piece_size2; j += default_block_size) { tree[tree.end_index() - blocks_in_piece + j / default_block_size] = hasher256(piece_buf.data() + j, std::min(default_block_size, piece_size2 - j)).final(); } merkle_fill_tree(tree, blocks_in_piece); ct.set_hash2(f, piece - first_piece, tree[0]); } if (!(flags & lt::create_torrent::v2_only)) { ct.set_hash(piece, hasher(piece_buf).final()); } } } return load_torrent_buffer(bencode(ct.generate())); } std::vector create_random_files(std::string const& path, span file_sizes) { std::vector fs; error_code ec; aux::vector random_data(300000); for (std::ptrdiff_t i = 0; i != file_sizes.size(); ++i) { aux::random_bytes(random_data); char filename[200]; std::snprintf(filename, sizeof(filename), "test%d", int(i)); char dirname[200]; std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5); std::string full_path = combine_path(path, dirname); lt::create_directories(full_path, ec); if (ec) std::printf("create_directory(%s) failed: (%d) %s\n" , full_path.c_str(), ec.value(), ec.message().c_str()); full_path = combine_path(full_path, filename); std::printf("creating file: %s\n", full_path.c_str()); int to_write = file_sizes[i]; fs.emplace_back(full_path, to_write); ofstream f(full_path.c_str()); while (to_write > 0) { int const s = std::min(to_write, static_cast(random_data.size())); f.write(random_data.data(), s); to_write -= s; } } return fs; } add_torrent_params create_torrent(std::ostream* file , char const* name, int piece_size , int num_pieces, bool add_tracker, lt::create_flags_t const flags , std::string ssl_certificate, bool const bad_v1_hashes) { // exercise the path when encountering invalid urls char const* invalid_tracker_url = "http:"; char const* invalid_tracker_protocol = "foo://non/existent-name.com/announce"; char const* valid_tracker_url = "http://foo.bar"; char const* valid_tracker_protocol = "udp://foo.bar/announce"; std::vector fs; int total_size = piece_size * num_pieces; fs.emplace_back(name, total_size); lt::create_torrent t(std::move(fs), piece_size, flags); if (add_tracker) { // invalid tracker urls won't be included in the generated magnet link t.add_tracker(invalid_tracker_url); t.add_tracker(invalid_tracker_protocol); // valid tracker urls will be included in the generated magnet link t.add_tracker(valid_tracker_url); t.add_tracker(valid_tracker_protocol); } if (!ssl_certificate.empty()) { std::vector file_buf; error_code ec; int res = load_file(ssl_certificate, file_buf, ec); if (ec || res < 0) { std::printf("failed to load SSL certificate: %s\n", ec.message().c_str()); } else { std::string pem; std::copy(file_buf.begin(), file_buf.end(), std::back_inserter(pem)); t.set_root_cert(pem); } } aux::vector piece(static_cast(piece_size)); for (int i = 0; i < piece.end_index(); ++i) piece[i] = (i % 26) + 'A'; if (!(flags & create_torrent::v2_only)) { sha1_hash const ph = bad_v1_hashes ? sha1_hash("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01") : hasher(piece).final(); for (auto const i : t.piece_range()) t.set_hash(i, ph); } if (!(flags & create_torrent::v1_only)) { int const blocks_in_piece = piece_size / default_block_size; aux::vector v2tree(merkle_num_nodes(merkle_num_leafs(blocks_in_piece))); for (int i = 0; i < blocks_in_piece; ++i) { sha256_hash const block_hash = hasher256(span(piece).subspan(i * default_block_size, default_block_size)).final(); v2tree[v2tree.end_index() - merkle_num_leafs(blocks_in_piece) + i] = block_hash; } merkle_fill_tree(v2tree, merkle_num_leafs(blocks_in_piece)); for (piece_index_t i(0); i < t.end_piece(); ++i) t.set_hash2(file_index_t{ 0 }, i - 0_piece, v2tree[0]); } if (file) { while (total_size > 0) { file->write(piece.data(), std::min(piece.end_index(), total_size)); total_size -= piece.end_index(); } } return load_torrent_buffer(bencode(t.generate())); } std::tuple setup_transfer(lt::session* ses1, lt::session* ses2, lt::session* ses3 , bool clear_files, bool use_metadata_transfer, bool connect_peers , std::string suffix, int piece_size , add_torrent_params const* atp , bool super_seeding , bool stop_lsd, bool use_ssl_ports , std::shared_ptr* torrent2, create_flags_t const flags , torrent_flags_t const seeder_extra_flags) { TORRENT_ASSERT(ses1); TORRENT_ASSERT(ses2); if (stop_lsd) { settings_pack pack; pack.set_bool(settings_pack::enable_lsd, false); ses1->apply_settings(pack); ses2->apply_settings(pack); if (ses3) ses3->apply_settings(pack); } // This has the effect of applying the global // rule to all peers, regardless of if they're local or not ip_filter f; f.add_rule(make_address_v4("0.0.0.0") , make_address_v4("255.255.255.255") , 1 << static_cast(lt::session::global_peer_class_id)); ses1->set_peer_class_filter(f); ses2->set_peer_class_filter(f); if (ses3) ses3->set_peer_class_filter(f); settings_pack pack; if (ses3) pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); pack.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); pack.set_int(settings_pack::max_failcount, 1); ses1->apply_settings(pack); ses2->apply_settings(pack); if (ses3) { ses3->apply_settings(pack); } add_torrent_params param; if (atp == nullptr || !atp->ti) { error_code ec; create_directory("tmp1" + suffix, ec); std::string const file_path = combine_path("tmp1" + suffix, "temporary"); ofstream file(file_path.c_str()); if (atp == nullptr) { param = ::create_torrent(&file, "temporary", piece_size, 9, false, flags); } else { auto temp = ::create_torrent(&file, "temporary", piece_size, 9, false, flags); param = *atp; param.ti = temp.ti; } file.close(); if (clear_files) { remove_all(combine_path("tmp2" + suffix, "temporary"), ec); remove_all(combine_path("tmp3" + suffix, "temporary"), ec); } std::printf("generated torrent: %s %s\n", aux::to_hex(param.ti->info_hashes().v2).c_str(), file_path.c_str()); } else { param = *atp; } auto ti = param.ti; // they should not use the same save dir, because the // file pool will complain if two torrents are trying to // use the same files param.flags &= ~torrent_flags::paused; param.flags &= ~torrent_flags::auto_managed; param.save_path = "tmp1" + suffix; // save flags before ORing in seeder-only bits so we can restore them for the downloader auto const downloader_flags = param.flags; param.flags |= torrent_flags::seed_mode | seeder_extra_flags; error_code ec; torrent_handle tor1 = ses1->add_torrent(param, ec); if (ec) { std::printf("ses1.add_torrent: %s\n", ec.message().c_str()); return std::make_tuple(torrent_handle(), torrent_handle(), torrent_handle()); } if (super_seeding) { tor1.set_flags(torrent_flags::super_seeding); } // the downloader cannot use seed_mode; restore the original flags minus seed_mode // (seeder_extra_flags may overlap with downloader flags, so restore rather than mask) param.flags = downloader_flags & ~torrent_flags::seed_mode; TEST_CHECK(!ses1->get_torrents().empty()); torrent_handle tor2; torrent_handle tor3; if (ses3) { param.save_path = "tmp3" + suffix; tor3 = ses3->add_torrent(param, ec); TEST_CHECK(!ses3->get_torrents().empty()); } if (use_metadata_transfer) { param.ti.reset(); param.info_hashes = ti->info_hashes(); } else if (torrent2) { param.ti = *torrent2; } else { param.ti = ti; } param.save_path = "tmp2" + suffix; tor2 = ses2->add_torrent(param, ec); TEST_CHECK(!ses2->get_torrents().empty()); TORRENT_ASSERT(ses1->get_torrents().size() == 1); TORRENT_ASSERT(ses2->get_torrents().size() == 1); // std::this_thread::sleep_for(lt::milliseconds(100)); if (connect_peers) { wait_for_downloading(*ses2, "ses2"); int port = 0; if (use_ssl_ports) { port = ses2->ssl_listen_port(); std::printf("%s: ses2->ssl_listen_port(): %d\n", time_now_string().c_str(), port); } if (port == 0) { port = ses2->listen_port(); std::printf("%s: ses2->listen_port(): %d\n", time_now_string().c_str(), port); } std::printf("%s: ses1: connecting peer port: %d\n" , time_now_string().c_str(), port); tor1.connect_peer(tcp::endpoint(make_address("127.0.0.1", ec) , std::uint16_t(port))); if (ses3) { // give the other peers some time to get an initial // set of pieces before they start sharing with each-other wait_for_downloading(*ses3, "ses3"); port = 0; int port2 = 0; if (use_ssl_ports) { port = ses2->ssl_listen_port(); port2 = ses1->ssl_listen_port(); } if (port == 0) port = ses2->listen_port(); if (port2 == 0) port2 = ses1->listen_port(); std::printf("ses3: connecting peer port: %d\n", port); tor3.connect_peer(tcp::endpoint( make_address("127.0.0.1", ec), std::uint16_t(port))); std::printf("ses3: connecting peer port: %d\n", port2); tor3.connect_peer(tcp::endpoint( make_address("127.0.0.1", ec) , std::uint16_t(port2))); } } return std::make_tuple(tor1, tor2, tor3); } namespace { pid_type web_server_pid = 0; } int start_web_server( bool ssl, bool chunked_encoding, bool keepalive, int min_interval, bool expect_host_header) { int const port = find_available_port(); char expected_host[200] = {}; if (expect_host_header) std::snprintf(expected_host, sizeof(expected_host), "127.0.0.1:%d", port); std::vector python_exes = get_python(); for (auto const& python_exe : python_exes) { char buf[300]; std::snprintf(buf, sizeof(buf), "%s .." SEPARATOR "web_server.py %d %d %d %d %d %s", python_exe.c_str(), port, chunked_encoding, ssl, keepalive, min_interval, expected_host); std::printf("%s starting web_server on port %d...\n", time_now_string().c_str(), port); std::printf("%s\n", buf); pid_type r = async_run(buf); if (r == 0) continue; web_server_pid = r; std::printf("%s launched\n", time_now_string().c_str()); std::this_thread::sleep_for(lt::milliseconds(1000)); wait_for_port(port); return port; } abort(); } void stop_web_server() { if (web_server_pid == 0) return; std::printf("stopping web server\n"); stop_process(web_server_pid); web_server_pid = 0; } namespace { pid_type websocket_server_pid = 0; } int start_websocket_server(bool ssl, int min_interval) { int port = 2000 + static_cast(aux::random(6000)); error_code ec; io_context ios; // make sure the port we pick is free do { ++port; tcp::socket s(ios); s.open(tcp::v4(), ec); if (ec) break; s.bind(tcp::endpoint(make_address("127.0.0.1") , std::uint16_t(port)), ec); } while (ec); std::vector python_exes = get_python(); for (auto const& python_exe : python_exes) { char buf[200]; std::snprintf(buf, sizeof(buf), "%s ../websocket_server.py %d %d %d" , python_exe.c_str(), port, ssl, min_interval); std::printf("%s starting websocket server on port %d...\n", time_now_string().c_str(), port); std::printf("%s\n", buf); pid_type r = async_run(buf); if (r == 0) continue; websocket_server_pid = r; std::printf("%s launched\n", time_now_string().c_str()); std::this_thread::sleep_for(lt::milliseconds(1000)); wait_for_port(port); return port; } abort(); } void stop_websocket_server() { if (websocket_server_pid == 0) return; std::printf("stopping websocket server\n"); stop_process(websocket_server_pid); websocket_server_pid = 0; } tcp::endpoint ep(char const* ip, int port) { error_code ec; tcp::endpoint ret(make_address(ip, ec), std::uint16_t(port)); TEST_CHECK(!ec); return ret; } udp::endpoint uep(char const* ip, int port) { error_code ec; udp::endpoint ret(make_address(ip, ec), std::uint16_t(port)); TEST_CHECK(!ec); return ret; } lt::address addr(char const* ip) { lt::error_code ec; auto ret = lt::make_address(ip, ec); TEST_CHECK(!ec); return ret; } lt::address_v4 addr4(char const* ip) { lt::error_code ec; auto ret = lt::make_address_v4(ip, ec); TEST_CHECK(!ec); return ret; } lt::address_v6 addr6(char const* ip) { lt::error_code ec; auto ret = lt::make_address_v6(ip, ec); TEST_CHECK(!ec); return ret; }