/* Copyright (c) 2017, BitTorrent Inc. Copyright (c) 2019-2020, Steven Siloti Copyright (c) 2020, Alden Torres Copyright (c) 2020-2021, Arvid Norberg All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #include #include "libtorrent/aux_/hash_picker.hpp" #include "libtorrent/peer_connection_interface.hpp" #include "libtorrent/aux_/stat.hpp" #include "libtorrent/aux_/merkle.hpp" #include "libtorrent/hex.hpp" #include "libtorrent/disk_interface.hpp" // for default_block_size #include "test.hpp" #include "test_utils.hpp" using namespace lt; #if 0 struct mock_peer_connection final : peer_connection_interface { tcp::endpoint const& remote() const override { return m_remote; } tcp::endpoint local_endpoint() const override { return {}; } void disconnect(error_code const&, operation_t, disconnect_severity_t) override {} peer_id const& pid() const override { return m_pid; } peer_id our_pid() const override { return m_pid; } void set_holepunch_mode() override {} aux::torrent_peer* peer_info_struct() const override { return m_torrent_peer; } void set_peer_info(aux::torrent_peer* pi) override { m_torrent_peer = pi; } bool is_outgoing() const override { return false; } void add_stat(std::int64_t, std::int64_t) override {} bool fast_reconnect() const override { return false; } bool is_choked() const override { return false; } bool failed() const override { return false; } aux::stat const& statistics() const override { return m_stat; } void get_peer_info(peer_info&) const override {} #ifndef TORRENT_DISABLE_LOGGING bool should_log(peer_log_alert::direction_t) const override { return true; } void peer_log(peer_log_alert::direction_t , char const*, char const*, ...) const noexcept override TORRENT_FORMAT(4, 5) {} #endif #if TORRENT_USE_I2P std::string const& destination() const override { static std::string const empty; return empty; } std::string const& local_i2p_endpoint() const override { static std::string const empty; return empty; } #endif aux::torrent_peer* m_torrent_peer; aux::stat m_stat; tcp::endpoint m_remote; peer_id m_pid; }; TORRENT_TEST(pick_piece_layer) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); fs.add_file("test/tmp2", 4 * 512 * 16 * 1024); aux::vector trees; trees.push_back(aux::merkle_tree(merkle_num_nodes(merkle_num_leafs(4 * 512)))); aux::from_hex("0000000000000000000000000000000000000000000000000000000000000001", trees.back()[0].data()); trees.push_back(aux::merkle_tree(merkle_num_nodes(merkle_num_leafs(4 * 512)))); aux::from_hex("0000000000000000000000000000000000000000000000000000000000000001", trees.back()[0].data()); hash_picker picker(fs, trees); typed_bitfield pieces; pieces.resize(8 * 512); pieces.set_all(); mock_peer_connection mock_peer1, mock_peer2; mock_peer1.m_torrent_peer = (torrent_peer*)0x1; mock_peer2.m_torrent_peer = (torrent_peer*)0x2; auto picked = picker.pick_hashes(pieces, 2, &mock_peer1); TEST_EQUAL(int(picked.size()), 2); TEST_EQUAL(picked[0].file, 0_file); TEST_EQUAL(picked[0].base, 0); TEST_EQUAL(picked[0].count, 512); TEST_EQUAL(picked[0].index, 0); TEST_EQUAL(picked[0].proof_layers, 10); TEST_EQUAL(picked[1].file, 0_file); TEST_EQUAL(picked[1].base, 0); TEST_EQUAL(picked[1].count, 512); TEST_EQUAL(picked[1].index, 512); TEST_EQUAL(picked[1].proof_layers, 10); picked = picker.pick_hashes(pieces, 3, &mock_peer2); TEST_EQUAL(int(picked.size()), 3); TEST_EQUAL(picked[0].file, 0_file); TEST_EQUAL(picked[0].base, 0); TEST_EQUAL(picked[0].count, 512); TEST_EQUAL(picked[0].index, 1024); TEST_EQUAL(picked[0].proof_layers, 10); TEST_EQUAL(picked[1].file, 0_file); TEST_EQUAL(picked[1].base, 0); TEST_EQUAL(picked[1].count, 512); TEST_EQUAL(picked[1].index, 1536); TEST_EQUAL(picked[1].proof_layers, 10); TEST_EQUAL(picked[2].file, 1_file); TEST_EQUAL(picked[2].base, 0); TEST_EQUAL(picked[2].count, 512); TEST_EQUAL(picked[2].index, 0); TEST_EQUAL(picked[2].proof_layers, 10); picked = picker.pick_hashes(pieces, 4, &mock_peer1); TEST_EQUAL(int(picked.size()), 3); TEST_EQUAL(picked[0].file, 1_file); TEST_EQUAL(picked[0].base, 0); TEST_EQUAL(picked[0].count, 512); TEST_EQUAL(picked[0].index, 512); TEST_EQUAL(picked[0].proof_layers, 10); TEST_EQUAL(picked[1].file, 1_file); TEST_EQUAL(picked[1].base, 0); TEST_EQUAL(picked[1].count, 512); TEST_EQUAL(picked[1].index, 1024); TEST_EQUAL(picked[1].proof_layers, 10); TEST_EQUAL(picked[2].file, 1_file); TEST_EQUAL(picked[2].base, 0); TEST_EQUAL(picked[2].count, 512); TEST_EQUAL(picked[2].index, 1536); TEST_EQUAL(picked[2].proof_layers, 10); } #endif namespace { sha256_hash from_hex(span str) { sha256_hash ret; aux::from_hex(str, ret.data()); return ret; } } TORRENT_TEST(reject_piece_request) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto const root = from_hex("0000000000000000000000000000000000000000000000000000000000000001"); trees.emplace_back(4 * 512, 1, root.data()); aux::hash_picker picker(fs, trees); typed_bitfield const pieces(4 * 512, true); auto const picked = picker.pick_hashes(pieces); picker.hashes_rejected(picked); auto const picked2 = picker.pick_hashes(pieces); TEST_CHECK(picked == picked2); } TORRENT_TEST(add_leaf_hashes) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 512); sha256_hash const root = full_tree[0]; trees.emplace_back(4 * 512, 1, root.data()); aux::hash_picker picker(fs, trees); std::vector hashes; auto const pieces_start = full_tree.end_index() - merkle_num_leafs(4 * 512); for (int i = 0; i < 512; ++i) hashes.push_back(full_tree[pieces_start + i]); for (int i = 3; i > 0; i = merkle_get_parent(i)) { hashes.push_back(full_tree[merkle_get_sibling(i)]); } aux::add_hashes_result result = picker.add_hashes(aux::hash_request(0_file, 0, 0, 512, 10) , hashes); TEST_CHECK(result.valid); result = picker.add_hashes(aux::hash_request(0_file, 0, 512, 512, 0) , span(full_tree).last(merkle_num_leafs(4 * 512) - 512).first(512)); TEST_CHECK(result.valid); hashes.clear(); for (int i = 1024; i < 1536; ++i) hashes.push_back(full_tree[pieces_start + i]); for (int i = 5; i > 0; i = merkle_get_parent(i)) { hashes.push_back(full_tree[merkle_get_sibling(i)]); } result = picker.add_hashes(aux::hash_request(0_file, 0, 1024, 512, 10) , hashes); TEST_CHECK(result.valid); result = picker.add_hashes(aux::hash_request(0_file, 0, 1536, 512, 0) , span(full_tree).last(merkle_num_leafs(4 * 512) - 1536).first(512)); TEST_CHECK(result.valid); TEST_CHECK(trees.front().build_vector() == full_tree); } TORRENT_TEST(add_piece_hashes) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 1024 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 1024); sha256_hash const root = full_tree[0]; trees.emplace_back(4 * 1024, 4, root.data()); aux::hash_picker picker(fs, trees); auto pieces_start = full_tree.begin() + merkle_num_nodes(1024) - 1024; std::vector hashes; std::copy(pieces_start, pieces_start + 512, std::back_inserter(hashes)); hashes.push_back(full_tree[2]); aux::add_hashes_result result = picker.add_hashes(aux::hash_request(0_file, 2, 0, 512, 9), hashes); TEST_CHECK(result.valid); hashes.clear(); std::copy(pieces_start + 512, pieces_start + 1024, std::back_inserter(hashes)); result = picker.add_hashes(aux::hash_request(0_file, 2, 512, 512, 8), hashes); TEST_CHECK(result.valid); auto const cmp = trees.front().build_vector(); TEST_CHECK(std::equal(cmp.begin(), cmp.begin() + merkle_num_nodes(1024), full_tree.begin())); } TORRENT_TEST(add_piece_hashes_marks_request_complete) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 512); sha256_hash const root = full_tree[0]; trees.emplace_back(4 * 512, 4, root.data()); aux::hash_picker picker(fs, trees); typed_bitfield const pieces(512, true); aux::hash_request const picked = picker.pick_hashes(pieces); TEST_EQUAL(picked.file, 0_file); TEST_EQUAL(picked.base, 2); TEST_EQUAL(picked.index, 0); TEST_EQUAL(picked.count, 512); // Make the same request immediately eligible again. This simulates the // retry interval expiring before the valid response is processed. picker.hashes_rejected(picked); auto pieces_start = full_tree.begin() + merkle_num_nodes(512) - 512; std::vector hashes; std::copy(pieces_start, pieces_start + 512, std::back_inserter(hashes)); aux::add_hashes_result const result = picker.add_hashes(picked, hashes); TEST_CHECK(result.valid); aux::hash_request const picked2 = picker.pick_hashes(pieces); TEST_EQUAL(picked2.count, 0); } TORRENT_TEST(add_piece_hashes_padded) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 1029 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 1029); sha256_hash const root = full_tree[0]; trees.emplace_back(4 * 1029, 4, root.data()); aux::hash_picker picker(fs, trees); auto pieces_start = merkle_num_nodes(merkle_num_leafs(1029)) - merkle_num_leafs(1029); std::vector hashes; // 5 hashes left after 1024 rounds up to 8, 1024 + 8 = 1032 std::copy(full_tree.begin() + pieces_start + 1024, full_tree.begin() + pieces_start + 1032 , std::back_inserter(hashes)); auto proof = merkle_get_parent(merkle_get_parent(merkle_get_parent(pieces_start + 1024))); while (proof > 0) { hashes.push_back(full_tree[merkle_get_sibling(proof)]); proof = merkle_get_parent(proof); } aux::add_hashes_result result = picker.add_hashes(aux::hash_request(0_file, 2, 1024, 8, 10), hashes); TEST_CHECK(result.valid); } TORRENT_TEST(add_piece_hashes_unpadded) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 1029 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 1029); sha256_hash const root = full_tree[0]; trees.emplace_back(4 * 1029, 4, root.data()); aux::hash_picker picker(fs, trees); auto pieces_start = merkle_num_nodes(merkle_num_leafs(1029)) - merkle_num_leafs(1029); std::vector hashes; std::copy(full_tree.begin() + pieces_start + 1024, full_tree.begin() + pieces_start + 1029 , std::back_inserter(hashes)); auto proof = merkle_get_parent(merkle_get_parent(merkle_get_parent(pieces_start + 1024))); while (proof > 0) { hashes.push_back(full_tree[merkle_get_sibling(proof)]); proof = merkle_get_parent(proof); } aux::add_hashes_result result = picker.add_hashes(aux::hash_request(0_file, 2, 1024, 5, 10), hashes); TEST_CHECK(result.valid); } TORRENT_TEST(add_bad_hashes) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 512); sha256_hash const root = full_tree[0]; trees.emplace_back(4 * 512, 4, root.data()); aux::hash_picker picker(fs, trees); // totally bogus hashes std::vector hashes(512); auto result = picker.add_hashes(aux::hash_request(0_file, 2, 0, 512, 0), hashes); TEST_CHECK(!result.valid); // bad proof hash hashes.clear(); auto const pieces_start = full_tree.end_index() - 512; for (int i = 0; i < 512; ++i) hashes.push_back(full_tree[pieces_start + i]); hashes.back()[1] ^= 0xaa; result = picker.add_hashes(aux::hash_request(0_file, 2, 0, 512, 0), hashes); TEST_CHECK(!result.valid); } TORRENT_TEST(bad_block_hash) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); auto const full_tree = build_tree(4 * 512); aux::vector trees; trees.emplace_back(4 * 512, 1, full_tree[0].data()); sha256_hash hash; aux::from_hex("0000000000000000000000000000000000000000000000000000000000000001" , hash.data()); trees.front().set_block(1, hash); aux::hash_picker picker(fs, trees); std::vector hashes; auto leafs_start = full_tree.end() - merkle_num_leafs(4 * 512); std::copy(leafs_start, leafs_start + 512, std::back_inserter(hashes)); for (int i = 3; i > 0; i = merkle_get_parent(i)) { hashes.push_back(full_tree[merkle_get_sibling(i)]); } aux::add_hashes_result result = picker.add_hashes(aux::hash_request(0_file, 0, 0, 512, 10) , hashes); TEST_CHECK(result.valid); TEST_CHECK((result.hash_failed == std::vector>>{{1_piece, {0}}})); } TORRENT_TEST(set_block_hash) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto const full_tree = build_tree(4 * 512); trees.emplace_back(4 * 512, 4, full_tree[0].data()); trees.front().load_tree(full_tree, bitfield(merkle_num_leafs(4 * 512))); int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); aux::hash_picker picker(fs, trees); auto result = picker.set_block_hash(1_piece, default_block_size , full_tree[first_leaf + 5]); TEST_CHECK(result.status == aux::set_block_hash_result::result::success); result = picker.set_block_hash(2_piece, default_block_size * 2 , full_tree[first_leaf + 10]); TEST_CHECK(result.status == aux::set_block_hash_result::result::success); result = picker.set_block_hash(2_piece, default_block_size * 2 , sha256_hash("01234567890123456789012345678901")); TEST_CHECK(result.status == aux::set_block_hash_result::result::block_hash_failed); } TORRENT_TEST(set_block_hash_fail) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto full_tree = build_tree(4 * 512); trees.emplace_back(4 * 512, 4, full_tree[0].data()); // zero out the inner nodes for a piece along with a single leaf node // then add a bogus hash for the leaf int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); full_tree[merkle_get_parent(first_leaf + 12)].clear(); full_tree[merkle_get_parent(first_leaf + 14)].clear(); full_tree[first_leaf + 13].clear(); trees.front().load_tree(full_tree, bitfield(merkle_num_leafs(4 * 512))); aux::hash_picker picker(fs, trees); TEST_CHECK(picker.set_block_hash(3_piece, 0, full_tree[first_leaf + 12]).status == aux::set_block_hash_result::result::unknown); TEST_CHECK(picker.set_block_hash(3_piece, 2 * default_block_size, full_tree[first_leaf + 14]).status == aux::set_block_hash_result::result::unknown); TEST_CHECK(picker.set_block_hash(3_piece, 3 * default_block_size, full_tree[first_leaf + 15]).status == aux::set_block_hash_result::result::unknown); auto const result = picker.set_block_hash(3_piece, default_block_size, sha256_hash("01234567890123456789012345678901")); TEST_CHECK(result.status == aux::set_block_hash_result::result::piece_hash_failed); TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 12)].is_all_zeros()); TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 13)].is_all_zeros()); TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 14)].is_all_zeros()); TEST_CHECK(trees.front()[merkle_get_parent(first_leaf + 15)].is_all_zeros()); } TORRENT_TEST(set_block_hash_pass) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; auto full_tree = build_tree(4 * 512); trees.emplace_back(4 * 512, 4, full_tree[0].data()); // zero out the inner nodes for a piece along with a single leaf node // then add a bogus hash for the leaf int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); full_tree[merkle_get_parent(first_leaf + 12)].clear(); full_tree[merkle_get_parent(first_leaf + 14)].clear(); auto const orig_hash = full_tree[first_leaf + 13]; full_tree[first_leaf + 13].clear(); trees.front().load_tree(full_tree, bitfield(merkle_num_leafs(4 * 512))); aux::hash_picker picker(fs, trees); TEST_CHECK(picker.set_block_hash(3_piece, 0, full_tree[first_leaf + 12]).status == aux::set_block_hash_result::result::unknown); TEST_CHECK(picker.set_block_hash(3_piece, 2 * default_block_size, full_tree[first_leaf + 14]).status == aux::set_block_hash_result::result::unknown); TEST_CHECK(picker.set_block_hash(3_piece, 3 * default_block_size, full_tree[first_leaf + 15]).status == aux::set_block_hash_result::result::unknown); auto const result = picker.set_block_hash(3_piece, default_block_size, orig_hash); TEST_CHECK(result.status == aux::set_block_hash_result::result::success); } TORRENT_TEST(pass_piece) { file_storage fs; fs.set_piece_length(4 * 16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); auto const full_tree = build_tree(4 * 512); aux::vector trees; sha256_hash root = full_tree[0]; trees.emplace_back(4 * 512, 4, root.data()); aux::hash_picker picker(fs, trees); int const first_leaf = full_tree.end_index() - merkle_num_leafs(4 * 512); for (int i = 0; i < 4; ++i) { auto result = picker.set_block_hash(0_piece, default_block_size * i , full_tree[first_leaf + i]); TEST_CHECK(result.status == aux::set_block_hash_result::result::unknown); } auto const pieces_start = full_tree.begin() + merkle_num_nodes(512) - 512; std::vector hashes; std::copy(pieces_start, pieces_start + 512, std::back_inserter(hashes)); aux::add_hashes_result result = picker.add_hashes(aux::hash_request(0_file, 2, 0, 512, 8), hashes); TEST_CHECK(result.valid); TEST_EQUAL(result.hash_passed.size(), 1); if (result.hash_passed.size() == 1) { TEST_EQUAL(result.hash_passed[0], 0_piece); } } TORRENT_TEST(only_pick_have_pieces) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 4 * 512 * 16 * 1024); aux::vector trees; sha256_hash root = from_hex("0000000000000000000000000000000000000000000000000000000000000001"); trees.emplace_back(4 * 512, 1, root.data()); aux::hash_picker picker(fs, trees); typed_bitfield pieces; pieces.resize(4 * 512); pieces.set_bit(512_piece); pieces.set_bit(1537_piece); std::vector picked; for (int i = 0; i < 3; ++i) picked.push_back(picker.pick_hashes(pieces)); TEST_EQUAL(picked[0].file, 0_file); TEST_EQUAL(picked[0].base, 0); TEST_EQUAL(picked[0].count, 512); TEST_EQUAL(picked[0].index, 512); TEST_EQUAL(picked[0].proof_layers, 10); TEST_EQUAL(picked[1].file, 0_file); TEST_EQUAL(picked[1].base, 0); TEST_EQUAL(picked[1].count, 512); TEST_EQUAL(picked[1].index, 1536); TEST_EQUAL(picked[1].proof_layers, 10); TEST_EQUAL(picked[2].count, 0); } TORRENT_TEST(validate_hash_request) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 2048 * 16 * 1024); // the merkle tree for this file has 2048 blocks int const num_leaves = merkle_num_leafs(2048); int const num_layers = merkle_num_layers(num_leaves); int const max = std::numeric_limits::max(); int const min = std::numeric_limits::min(); // hash_request make function // (file_index_t const f, int const b, int const i, int const c, int const p) using hash_request = aux::hash_request; TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); // file index out-of-range TEST_CHECK(!validate_hash_request(hash_request(file_index_t{1}, 0, 0, 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{-1}, 0, 0, 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{max}, 0, 0, 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{min}, 0, 0, 1, 0), fs)); // base out-of-range TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, -1, 0, 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, num_layers, 0, 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, max, 0, 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, min, 0, 1, 0), fs)); // base in-range TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, num_layers-1, 0, 1, 0), fs)); // count out-of-range // the upper limit of count depends on base and index TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, 0, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, num_leaves + 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 100, num_leaves - 100 + 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, 8193, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, min, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, max, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, num_leaves / 2 + 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 2, 0, num_leaves / 4 + 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 3, 0, num_leaves / 8 + 1, 0), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 3, 5, num_leaves / 8 - 5 + 1, 0), fs)); // count in-range TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 100, num_leaves - 100, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, num_leaves, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 1, 0, num_leaves / 2, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 2, 0, num_leaves / 4, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 3, 0, num_leaves / 8, 0), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 3, 5, num_leaves / 8 - 5, 0), fs)); // proof_layers out-of-range TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, num_layers), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, num_layers), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, min), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, max), fs)); TEST_CHECK(!validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, -1), fs)); // proof_layers in-range TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 0, 0, 1, num_layers - 1), fs)); TEST_CHECK(validate_hash_request(hash_request(file_index_t{0}, 1, 0, 1, num_layers - 2), fs)); } TORRENT_TEST(validate_hash_request_pad_file) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 2048 * 16 * 1024); fs.add_file(".pad/16384", 16 * 1024, file_storage::flag_pad_file); // sanity: requests for the regular file are still accepted TEST_CHECK(validate_hash_request(aux::hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); // requests targeting the pad file must be rejected. The pad file's // merkle tree is uninitialized, so allowing such a request would // otherwise reach merkle_tree::add_hashes with an empty m_tree. TEST_CHECK(!validate_hash_request(aux::hash_request(file_index_t{1}, 0, 0, 1, 0), fs)); } TORRENT_TEST(validate_hash_request_empty_file) { file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 2048 * 16 * 1024); fs.add_file("test/empty", 0); // sanity: requests for the regular file are still accepted TEST_CHECK(validate_hash_request(aux::hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); // requests targeting the zero-size file must be rejected. Like pad // files, its merkle tree is uninitialized. TEST_CHECK(!validate_hash_request(aux::hash_request(file_index_t{1}, 0, 0, 1, 0), fs)); } TORRENT_TEST(validate_hash_request_single_block_file) { // For a file that fits in a single block, the merkle tree has one leaf // and that leaf IS the root, so no hash exchange is ever needed. The // tree has 0 layers above the root, so every request — including // base=0, index=0, count=1 — must be rejected. file_storage fs; fs.set_piece_length(16 * 1024); fs.add_file("test/tmp1", 16 * 1024); TEST_EQUAL(merkle_num_leafs(fs.file_num_blocks(file_index_t{0})), 1); TEST_EQUAL(merkle_num_layers(merkle_num_leafs(fs.file_num_blocks(file_index_t{0}))), 0); TEST_CHECK(!validate_hash_request(aux::hash_request(file_index_t{0}, 0, 0, 1, 0), fs)); }