Files
libtorrent/test/test_create_torrent.cpp

1432 lines
44 KiB
C++

/*
Copyright (c) 2016, 2021, Alden Torres
Copyright (c) 2016-2022, Arvid Norberg
Copyright (c) 2016, Pavel Pimenov
Copyright (c) 2017-2018, Steven Siloti
All rights reserved.
You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/
#include "test.hpp"
#include "test_utils.hpp"
#include "setup_transfer.hpp"
#include "libtorrent/torrent_info.hpp"
#include "libtorrent/create_torrent.hpp"
#include "libtorrent/bencode.hpp"
#include "libtorrent/announce_entry.hpp"
#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix
#include "libtorrent/announce_entry.hpp"
#include "libtorrent/units.hpp"
#include "libtorrent/load_torrent.hpp" // for load_torrent_buffer
#include "libtorrent/file_storage.hpp"
#include "libtorrent/aux_/path.hpp"
#include "libtorrent/units.hpp"
#include "libtorrent/aux_/vector.hpp"
#include "libtorrent/write_resume_data.hpp" // for write_torrent_file
#include "libtorrent/add_torrent_params.hpp"
#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
using namespace std::literals::string_literals;
namespace {
constexpr lt::aux::path_index_t operator""_path(unsigned long long i)
{ return lt::aux::path_index_t(static_cast<std::uint32_t>(i)); }
}
#if TORRENT_ABI_VERSION < 4
// make sure creating a torrent from an existing handle preserves the
// info-dictionary verbatim, so as to not alter the info-hash
TORRENT_TEST(create_verbatim_torrent)
{
char const test_torrent[] = "d4:infod4:name6:foobar6:lengthi12345e"
"12:piece lengthi65536e6:pieces20:ababababababababababee";
lt::torrent_info info(test_torrent, lt::from_span);
lt::create_torrent t(info);
std::vector<char> buffer = bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
// now, make sure the info dictionary was unchanged
buffer.push_back('\0');
char const* dest_info = std::strstr(&buffer[0], "4:info");
TEST_CHECK(dest_info != nullptr);
// +1 and -2 here is to strip the outermost dictionary from the source
// torrent, since create_torrent may have added items next to the info dict
TEST_CHECK(memcmp(dest_info, test_torrent + 1, sizeof(test_torrent)-3) == 0);
}
#endif
TORRENT_TEST(auto_piece_size)
{
std::int64_t const kiB = 1024;
std::int64_t const MiB = 1024 * 1024;
std::int64_t const GiB = 1024 * 1024 * 1024;
std::array<std::pair<std::int64_t, std::int64_t>, 11> samples{{
{100LL, 16 * kiB},
{3 * MiB, 32 * kiB},
{11 * MiB, 64 * kiB},
{43 * MiB, 128 * kiB},
{172 * MiB, 256 * kiB},
{688 * MiB, 512 * kiB},
{3 * GiB, 1 * MiB},
{11 * GiB, 2 * MiB},
{44 * GiB, 4 * MiB},
{176 * GiB, 8 * MiB},
{704 * GiB, 16 * MiB},
}};
for (auto const& t : samples)
{
#if TORRENT_ABI_VERSION < 4
{
lt::file_storage fs;
fs.add_file("a", t.first);
lt::create_torrent ct(fs, 0);
TEST_CHECK(ct.piece_length() == static_cast<int>(t.second));
}
#endif
{
std::vector<lt::create_file_entry> files;
files.emplace_back("a", t.first);
lt::create_torrent ct(std::move(files), 0);
TEST_CHECK(ct.piece_length() == static_cast<int>(t.second));
}
}
}
namespace {
int test_piece_size(int const piece_size, lt::create_flags_t const f = {})
{
std::int64_t const MiB = 1024 * 1024;
std::vector<lt::create_file_entry> files;
files.emplace_back("a", 100 * MiB);
lt::create_torrent ct(std::move(files), piece_size, f);
#if TORRENT_ABI_VERSION < 4
{
lt::file_storage fs;
fs.add_file("a", 100 * MiB);
lt::create_torrent ct2(fs, piece_size, f);
TEST_EQUAL(ct2.piece_length(), ct.piece_length());
}
#endif
return ct.piece_length();
}
}
TORRENT_TEST(piece_size_restriction_16kB)
{
// v2 torrents must have piece sizes of at least 16 kiB
TEST_EQUAL(test_piece_size(15000), 16 * 1024);
TEST_EQUAL(test_piece_size(500), 16 * 1024);
TEST_THROW(test_piece_size(15000, lt::create_torrent::v1_only));
TEST_THROW(test_piece_size(8000, lt::create_torrent::v1_only));
TEST_EQUAL(test_piece_size(8192, lt::create_torrent::v1_only), 8192);
}
TORRENT_TEST(piece_size_quanta)
{
// v2 torrents must have power-of-two piece sizes
TEST_EQUAL(test_piece_size(32 * 1024), 32 * 1024);
TEST_EQUAL(test_piece_size(32 * 1024, lt::create_torrent::v1_only), 32 * 1024);
TEST_THROW(test_piece_size(48 * 1024));
TEST_EQUAL(test_piece_size(48 * 1024, lt::create_torrent::v1_only), 48 * 1024);
TEST_THROW(test_piece_size(47 * 1024, lt::create_torrent::v1_only));
TEST_THROW(test_piece_size(47 * 1024));
}
TORRENT_TEST(piece_size_limit)
{
// the max piece size is determined by piece_picker::max_blocks_per_piece
TEST_EQUAL(test_piece_size(0x8000000), 0x8000000);
TEST_THROW(test_piece_size(0x8000001, lt::create_torrent::v1_only));
TEST_THROW(test_piece_size(0x10000000));
}
#if TORRENT_ABI_VERSION < 4
TORRENT_TEST(create_torrent_round_trip)
{
char const test_torrent[] = "d8:announce26:udp://testurl.com/announce7:comment22:this is a test comment13:creation datei1337e4:infod6:lengthi12345e4:name6:foobar12:piece lengthi65536e6:pieces20:ababababababababababee";
lt::torrent_info info1(test_torrent, lt::from_span);
TEST_EQUAL(info1.comment(), "this is a test comment");
TEST_EQUAL(info1.trackers().size(), 1);
TEST_EQUAL(info1.trackers().front().url, "udp://testurl.com/announce");
lt::create_torrent t(info1);
std::vector<char> buffer = bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
lt::torrent_info info2(buffer, lt::from_span);
TEST_EQUAL(info2.comment(), "this is a test comment");
TEST_EQUAL(info2.trackers().size(), 1);
TEST_EQUAL(info2.trackers().front().url, "udp://testurl.com/announce");
TEST_CHECK(info1.info_hashes() == info2.info_hashes());
TEST_CHECK(info2.hash_for_piece(0_piece) == info1.hash_for_piece(0_piece));
}
#endif
namespace {
void test_round_trip_torrent(std::string const& name)
{
std::string const root_dir = lt::parent_path(lt::current_working_directory());
std::string const filename = lt::combine_path(lt::combine_path(root_dir, "test_torrents"), name);
std::vector<char> v2_buffer;
lt::error_code ec;
int const ret = load_file(filename, v2_buffer, ec);
TEST_CHECK(ret == 0);
TEST_CHECK(!ec);
lt::bdecode_node in_torrent = lt::bdecode(v2_buffer);
#if TORRENT_ABI_VERSION < 4
{
lt::torrent_info info1(v2_buffer, lt::from_span);
lt::create_torrent t(info1);
std::vector<char> const out_buffer = bencode(t.generate());
lt::bdecode_node out_torrent = lt::bdecode(out_buffer);
TEST_CHECK(out_buffer == t.generate_buf());
TEST_CHECK(out_torrent.dict_find("info").data_section()
== in_torrent.dict_find("info").data_section());
auto in_piece_layers = in_torrent.dict_find("piece layers").data_section();
auto out_piece_layers = out_torrent.dict_find("piece layers").data_section();
TEST_CHECK(out_piece_layers == in_piece_layers);
}
#endif
auto atp = lt::load_torrent_buffer(v2_buffer);
lt::entry e = lt::write_torrent_file(atp);
std::vector<char> out_buffer = bencode(e);
lt::bdecode_node out_torrent = lt::bdecode(out_buffer);
TEST_CHECK(out_torrent.dict_find("info").data_section()
== in_torrent.dict_find("info").data_section());
#if TORRENT_ABI_VERSION < 4
auto in_piece_layers = in_torrent.dict_find("piece layers").data_section();
auto out_piece_layers = out_torrent.dict_find("piece layers").data_section();
TEST_CHECK(out_piece_layers == in_piece_layers);
#endif
}
}
TORRENT_TEST(create_torrent_round_trip_v2)
{
test_round_trip_torrent("v2_only.torrent");
}
TORRENT_TEST(create_torrent_round_trip_hybrid_missing_tailpad)
{
test_round_trip_torrent("v2_hybrid-missing-tailpad.torrent");
}
TORRENT_TEST(create_torrent_round_trip_hybrid)
{
test_round_trip_torrent("v2_hybrid.torrent");
}
TORRENT_TEST(create_torrent_round_trip_empty_file)
{
test_round_trip_torrent("v2_empty_file.torrent");
}
// check that attempting to create a torrent containing both
// a file and directory with the same name is not allowed
TORRENT_TEST(v2_path_conflict)
{
std::vector<lt::create_file_entry> fs;
for (int i = 0; i < 2; ++i)
{
switch (i)
{
case 0:
fs.emplace_back("test/A/tmp", 0x4000);
fs.emplace_back("test/a", 0x4000);
fs.emplace_back("test/A", 0x4000);
fs.emplace_back("test/filler", 0x4000);
break;
case 1:
fs.emplace_back("test/long/path/name/that/collides", 0x4000);
fs.emplace_back("test/long/path", 0x4000);
fs.emplace_back("test/filler-1", 0x4000);
fs.emplace_back("test/filler-2", 0x4000);
break;
}
lt::create_torrent t(std::move(fs), 0x4000);
lt::sha256_hash const dummy("01234567890123456789012345678901");
lt::piece_index_t::diff_type zero(0);
t.set_hash2(0_file, zero, dummy);
t.set_hash2(1_file, zero, dummy);
t.set_hash2(2_file, zero, dummy);
t.set_hash2(3_file, zero, dummy);
TEST_THROW(t.generate());
TEST_THROW(t.generate_buf());
}
}
TORRENT_TEST(v2_only)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/A", 0x8002);
fs.emplace_back("test/B", 0x4002);
lt::create_torrent t(std::move(fs), 0x4000, lt::create_torrent::v2_only);
using p = lt::piece_index_t::diff_type;
t.set_hash2(0_file, p(0), lt::sha256_hash::max());
t.set_hash2(0_file, p(1), lt::sha256_hash::max());
t.set_hash2(0_file, p(2), lt::sha256_hash::max());
// file 1 is a pad file
t.set_hash2(2_file, p(0), lt::sha256_hash::max());
t.set_hash2(2_file, p(1), lt::sha256_hash::max());
std::vector<char> const buffer = lt::bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
#if TORRENT_ABI_VERSION < 4
{
lt::torrent_info info(buffer, lt::from_span);
TEST_CHECK(info.info_hashes().has_v2());
TEST_CHECK(!info.info_hashes().has_v1());
TEST_EQUAL(info.layout().file_name(0_file), "A");
TEST_CHECK(info.layout().pad_file_at(1_file));
TEST_EQUAL(info.layout().file_name(2_file), "B");
TEST_EQUAL(info.name(), "test");
lt::create_torrent t2(info);
std::vector<char> const buffer2 = bencode(t2.generate());
TEST_CHECK(buffer2 == t2.generate_buf());
TEST_CHECK(buffer == buffer2);
}
#endif
std::shared_ptr<lt::torrent_info const> info = lt::load_torrent_buffer(buffer).ti;
TEST_CHECK(info->info_hashes().has_v2());
TEST_CHECK(!info->info_hashes().has_v1());
TEST_EQUAL(info->layout().file_name(0_file), "A");
TEST_CHECK(info->layout().pad_file_at(1_file));
TEST_EQUAL(info->layout().file_name(2_file), "B");
TEST_EQUAL(info->name(), "test");
}
TORRENT_TEST(v2_only_set_hash)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/A", 0x8002);
lt::create_torrent t(std::move(fs), 0x4000, lt::create_torrent::v2_only);
TEST_THROW(t.set_hash(0_piece, lt::sha1_hash::max()));
}
#if TORRENT_HAS_SYMLINK || !defined TORRENT_WINDOWS
namespace {
void check(int ret)
{
if (ret == 0) return;
lt::error_code ec(errno, lt::generic_category());
std::cerr << "call failed: " << ec.message() << '\n';
throw std::runtime_error(ec.message());
}
}
#endif
#if TORRENT_HAS_SYMLINK
TORRENT_TEST(create_torrent_symlink)
{
lt::error_code ec;
lt::create_directories("test-torrent/a/b/c", ec);
lt::create_directories("test-torrent/d/", ec);
std::ofstream f1("test-torrent/a/b/c/file-1");
check(::truncate("test-torrent/a/b/c/file-1", 1000));
std::ofstream f2("test-torrent/d/file-2");
check(::truncate("test-torrent/d/file-2", 1000));
check(::symlink("../a/b/c/file-1", "test-torrent/d/test-link-1"));
check(::symlink("a/b/c/file-1", "test-torrent/test-link-2"));
check(::symlink("file-1", "test-torrent/a/b/c/test-link-3"));
check(::symlink("../../../d/file-2", "test-torrent/a/b/c/test-link-4"));
auto check = [](lt::create_torrent& t) {
lt::set_piece_hashes(t, ".", [] (lt::piece_index_t) {});
std::vector<char> torrent = lt::bencode(t.generate());
TEST_CHECK(torrent == t.generate_buf());
lt::add_torrent_params atp = lt::load_torrent_buffer(torrent);
auto& ti = *atp.ti;
// directly inspect the bencoded v1 files list to confirm the
// stored "symlink path" entries are relative to the torrent root
// and do NOT have the torrent's own name as their first element
lt::bdecode_node const root = lt::bdecode(torrent);
lt::bdecode_node const files_list = root.dict_find("info").dict_find_list("files");
TEST_CHECK(files_list);
int direct_found = 0;
for (int i = 0; i < files_list.list_size(); ++i)
{
lt::bdecode_node const file_e = files_list.list_at(i);
lt::bdecode_node const sympath = file_e.dict_find_list("symlink path");
if (!sympath) continue;
TEST_CHECK(sympath.list_size() > 0);
// the torrent name "test-torrent" must not appear as the first
// component of any stored symlink path
TEST_CHECK(sympath.list_at(0).string_value() != "test-torrent");
++direct_found;
}
TEST_EQUAL(direct_found, 4);
int found = 0;
for (auto i : ti.layout().file_range())
{
auto const filename = ti.layout().file_path(i);
if (filename == "test-torrent/d/test-link-1"
|| filename == "test-torrent/test-link-2"
|| filename == "test-torrent/a/b/c/test-link-3")
{
TEST_EQUAL(ti.layout().symlink(i), "test-torrent/a/b/c/file-1");
++found;
}
else if (filename == "test-torrent/a/b/c/test-link-4")
{
TEST_EQUAL(ti.layout().symlink(i), "test-torrent/d/file-2");
++found;
}
}
TEST_EQUAL(found, 4);
};
#if TORRENT_ABI_VERSION < 4
{
lt::file_storage fs;
lt::add_files(fs, "test-torrent"
, [](std::string n){ std::cout << n << '\n'; return true; }, lt::create_torrent::symlinks);
lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks);
check(t);
}
#endif
{
auto files = lt::list_files("test-torrent"
, [](std::string n){ std::cout << n << '\n'; return true; }, lt::create_torrent::symlinks);
lt::create_torrent t(std::move(files), 16 * 1024, lt::create_torrent::symlinks);
check(t);
}
}
TORRENT_TEST(create_torrent_symlink_escape)
{
lt::error_code ec;
lt::create_directories("escape-torrent/sub", ec);
TEST_CHECK(!ec);
std::ofstream f1("escape-torrent/sub/inside-file");
check(::truncate("escape-torrent/sub/inside-file", 1000));
// these symlinks all reach outside the torrent root and must be
// silently dropped from the resulting file list
check(::symlink("../../outside", "escape-torrent/escape-1"));
check(::symlink("../../../some/other/file", "escape-torrent/sub/escape-2"));
check(::symlink("/tmp/elsewhere", "escape-torrent/escape-3"));
// this one stays inside the torrent root and must be kept
check(::symlink("inside-file", "escape-torrent/sub/inside-link"));
auto check = [](lt::create_torrent& t) {
lt::set_piece_hashes(t, ".", [](lt::piece_index_t) {});
std::vector<char> torrent = lt::bencode(t.generate());
TEST_CHECK(torrent == t.generate_buf());
lt::add_torrent_params atp = lt::load_torrent_buffer(torrent);
auto& ti = *atp.ti;
bool found_inside_link = false;
for (auto i : ti.layout().file_range())
{
auto const filename = ti.layout().file_path(i);
// none of the escaping symlinks should appear in the torrent
TEST_CHECK(filename != "escape-torrent/escape-1");
TEST_CHECK(filename != "escape-torrent/sub/escape-2");
TEST_CHECK(filename != "escape-torrent/escape-3");
if (filename == "escape-torrent/sub/inside-link")
{
TEST_EQUAL(ti.layout().symlink(i), "escape-torrent/sub/inside-file");
found_inside_link = true;
}
}
TEST_CHECK(found_inside_link);
};
#if TORRENT_ABI_VERSION < 4
{
lt::file_storage fs;
lt::add_files(
fs, "escape-torrent", [](std::string) { return true; }, lt::create_torrent::symlinks);
lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks);
check(t);
}
#endif
{
auto files = lt::list_files(
"escape-torrent",
[](std::string n) {
std::cout << n << '\n';
return true;
},
lt::create_torrent::symlinks);
lt::create_torrent t(std::move(files), 16 * 1024, lt::create_torrent::symlinks);
check(t);
}
}
TORRENT_TEST(create_torrent_symlink_pred)
{
lt::error_code ec;
lt::create_directories("pred-torrent/sub", ec);
std::ofstream f1("pred-torrent/sub/keep-me");
check(::truncate("pred-torrent/sub/keep-me", 1000));
std::ofstream f2("pred-torrent/sub/skip-me");
check(::truncate("pred-torrent/sub/skip-me", 1000));
// two symlinks: one to a file the predicate accepts, one to a file
// the predicate rejects
check(::symlink("sub/keep-me", "pred-torrent/link-keep"));
check(::symlink("sub/skip-me", "pred-torrent/link-skip"));
auto check = [](lt::create_torrent& t) {
lt::set_piece_hashes(t, ".", [](lt::piece_index_t) {});
std::vector<char> torrent = lt::bencode(t.generate());
TEST_CHECK(torrent == t.generate_buf());
lt::add_torrent_params atp = lt::load_torrent_buffer(torrent);
auto& ti = *atp.ti;
bool found_link_keep = false;
for (auto i : ti.layout().file_range())
{
auto const filename = ti.layout().file_path(i);
// link-skip points at skip-me which the predicate rejected
TEST_CHECK(filename != "pred-torrent/link-skip");
// the rejected regular file itself must also be absent
TEST_CHECK(filename != "pred-torrent/sub/skip-me");
if (filename == "pred-torrent/link-keep")
{
TEST_EQUAL(ti.layout().symlink(i), "pred-torrent/sub/keep-me");
found_link_keep = true;
}
}
TEST_CHECK(found_link_keep);
};
#if TORRENT_ABI_VERSION < 4
{
lt::file_storage fs;
lt::add_files(
fs,
"pred-torrent",
[](std::string n) {
// reject the regular file "skip-me" AND any symlink whose
// resolved target is "skip-me". The second pred call (on the
// resolved target) is what we're testing.
return n.find("skip-me") == std::string::npos;
},
lt::create_torrent::symlinks);
lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks);
check(t);
}
#endif
{
auto files = lt::list_files(
"pred-torrent",
[](std::string n) {
// reject the regular file "skip-me" AND any symlink whose
// resolved target is "skip-me". The second pred call (on the
// resolved target) is what we're testing.
return n.find("skip-me") == std::string::npos;
},
lt::create_torrent::symlinks);
lt::create_torrent t(std::move(files), 16 * 1024, lt::create_torrent::symlinks);
check(t);
}
}
#endif
#ifndef TORRENT_WINDOWS
TORRENT_TEST(v2_attributes)
{
std::ofstream f1("file-1");
check(::truncate("file-1", 1000));
check(::chmod("file-1", S_IWUSR | S_IRUSR | S_IXUSR));
auto check = [](lt::create_torrent& t) {
lt::set_piece_hashes(t, ".", [] (lt::piece_index_t) {});
lt::entry e = t.generate();
std::cout << e.to_string() << '\n';
TEST_EQUAL(e["info"]["attr"].string(), "x");
TEST_EQUAL(e["info"]["file tree"]["file-1"][""]["attr"].string(), "x");
};
#if TORRENT_ABI_VERSION < 4
{
lt::file_storage fs;
lt::add_files(fs, "file-1");
lt::create_torrent t(fs, 16 * 1024, {});
check(t);
}
#endif
{
auto files = lt::list_files("file-1");
lt::create_torrent t(std::move(files), 16 * 1024, {});
check(t);
}
}
#endif
TORRENT_TEST(v1_only_set_hash2)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/A", 0x8002);
lt::create_torrent t(std::move(fs), 0x4000, lt::create_torrent::v1_only);
using p = lt::piece_index_t::diff_type;
TEST_THROW(t.set_hash2(0_file, p(0), lt::sha256_hash::max()));
}
// if we don't specify a v2-only flag, but only set v2 hashes, the created
// torrent is implicitly v2-only
TORRENT_TEST(implicit_v2_only)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/A", 0x8002);
fs.emplace_back("test/B", 0x4002);
lt::create_torrent t(std::move(fs), 0x4000);
using p = lt::piece_index_t::diff_type;
t.set_hash2(0_file, p(0), lt::sha256_hash::max());
t.set_hash2(0_file, p(1), lt::sha256_hash::max());
t.set_hash2(0_file, p(2), lt::sha256_hash::max());
// file 1 is a pad file
t.set_hash2(2_file, p(0), lt::sha256_hash::max());
t.set_hash2(2_file, p(1), lt::sha256_hash::max());
std::vector<char> const buffer = lt::bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
{
lt::add_torrent_params atp = lt::load_torrent_buffer(buffer);
auto& info = *atp.ti;
TEST_CHECK(info.info_hashes().has_v2());
TEST_CHECK(!info.info_hashes().has_v1());
TEST_EQUAL(info.layout().file_name(0_file), "A");
TEST_EQUAL(info.layout().pad_file_at(1_file), true);
TEST_EQUAL(info.layout().file_name(2_file), "B");
TEST_EQUAL(info.name(), "test");
}
auto atp = lt::load_torrent_buffer(buffer);
TEST_CHECK(atp.ti->info_hashes().has_v2());
TEST_CHECK(!atp.ti->info_hashes().has_v1());
TEST_EQUAL(atp.ti->layout().file_name(0_file), "A");
TEST_EQUAL(atp.ti->layout().pad_file_at(1_file), true);
TEST_EQUAL(atp.ti->layout().file_name(2_file), "B");
TEST_EQUAL(atp.ti->name(), "test");
}
// if we don't specify a v1-only flag, but only set v1 hashes, the created
// torrent is implicitly v1-only
TORRENT_TEST(implicit_v1_only)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/A", 0x8002);
fs.emplace_back("test/B", 0x4002);
lt::create_torrent t(std::move(fs), 0x4000);
for (lt::piece_index_t i : t.piece_range())
t.set_hash(i, lt::sha1_hash::max());
std::vector<char> const buffer = lt::bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
{
lt::add_torrent_params atp = lt::load_torrent_buffer(buffer);
auto& info = *atp.ti;
TEST_CHECK(!info.info_hashes().has_v2());
TEST_CHECK(info.info_hashes().has_v1());
TEST_EQUAL(info.layout().file_name(0_file), "A");
TEST_EQUAL(info.layout().pad_file_at(1_file), true);
TEST_EQUAL(info.layout().file_name(2_file), "B");
TEST_EQUAL(info.name(), "test");
}
auto info = lt::load_torrent_buffer(buffer).ti;
TEST_CHECK(!info->info_hashes().has_v2());
TEST_CHECK(info->info_hashes().has_v1());
TEST_EQUAL(info->layout().file_name(0_file), "A");
TEST_EQUAL(info->layout().pad_file_at(1_file), true);
TEST_EQUAL(info->layout().file_name(2_file), "B");
TEST_EQUAL(info->name(), "test");
}
namespace {
template <typename Fun>
lt::add_torrent_params test_field(Fun f)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("A", 0x4000);
lt::create_torrent t(std::move(fs), 0x4000);
for (lt::piece_index_t i : t.piece_range())
t.set_hash(i, lt::sha1_hash::max());
f(t);
std::vector<char> const buffer = lt::bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
return lt::load_torrent_buffer(buffer);
}
}
TORRENT_TEST(no_creation_date)
{
auto atp = test_field([](lt::create_torrent& t){
t.set_creation_date(0);
});
TEST_EQUAL(atp.creation_date, 0);
}
TORRENT_TEST(creation_date)
{
auto atp = test_field([](lt::create_torrent& t){
t.set_creation_date(1337);
});
TEST_EQUAL(atp.creation_date, 1337);
}
TORRENT_TEST(comment)
{
auto atp = test_field([](lt::create_torrent& t){
t.set_comment("foobar");
});
TEST_EQUAL(atp.comment, "foobar");
}
TORRENT_TEST(creator)
{
auto atp = test_field([](lt::create_torrent& t){
t.set_creator("foobar");
});
TEST_EQUAL(atp.created_by, "foobar");
}
TORRENT_TEST(dht_nodes)
{
auto atp = test_field([](lt::create_torrent& t){
t.add_node({"foobar"s, 1337});
});
using nodes = std::vector<std::pair<std::string, int>>;
TEST_CHECK((atp.dht_nodes == nodes{{"foobar", 1337}}));
}
TORRENT_TEST(ssl_cert)
{
auto atp = test_field([](lt::create_torrent& t){
t.set_root_cert("foobar");
});
TEST_EQUAL(atp.ti->ssl_cert(), "foobar");
}
TORRENT_TEST(priv)
{
auto atp = test_field([](lt::create_torrent& t){
t.set_priv(true);
});
TEST_CHECK(atp.ti->priv());
}
TORRENT_TEST(piece_layer)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/large", 0x8000);
fs.emplace_back("test/small-1", 0x4000);
fs.emplace_back("test/small-2", 0x3fff);
lt::create_torrent t(std::move(fs), 0x4000);
using p = lt::piece_index_t::diff_type;
t.set_hash2(0_file, p(0), lt::sha256_hash::max());
t.set_hash2(0_file, p(1), lt::sha256_hash::max());
t.set_hash2(1_file, p(0), lt::sha256_hash::max());
t.set_hash2(2_file, p(0), lt::sha256_hash::max());
std::vector<char> const buffer = lt::bencode(t.generate());
TEST_CHECK(buffer == t.generate_buf());
auto atp = lt::load_torrent_buffer(buffer);
TEST_EQUAL(atp.merkle_trees.size(), 4);
TEST_EQUAL(atp.merkle_trees[0_file].size(), 2);
TEST_EQUAL(atp.merkle_trees[1_file].size(), 0);
TEST_EQUAL(atp.merkle_trees[2_file].size(), 0);
TEST_EQUAL(atp.merkle_trees[3_file].size(), 0);
#if TORRENT_ABI_VERSION < 4
auto info = std::make_shared<lt::torrent_info>(buffer, lt::from_span);
TEST_EQUAL(info->piece_layer(0_file).size(), lt::sha256_hash::size() * 2);
TEST_EQUAL(info->piece_layer(1_file).size(), lt::sha256_hash::size());
TEST_EQUAL(info->piece_layer(2_file).size(), lt::sha256_hash::size());
TEST_EQUAL(info->piece_layer(3_file).size(), 0);
#endif
}
TORRENT_TEST(pieces_root_empty_file)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-empty", 0);
fs.emplace_back("test/2-small", 0x3fff);
fs.emplace_back("test/3-empty", 0);
lt::create_torrent t(std::move(fs), 0x4000);
using p = lt::piece_index_t::diff_type;
t.set_hash2(1_file, p(0), lt::sha256_hash::max());
lt::entry e = t.generate();
TEST_CHECK(!e["info"]["files tree"]["test"]["1-empty"].find_key("pieces root"));
TEST_CHECK(!e["info"]["files tree"]["test"]["2-small"].find_key("pieces root"));
TEST_CHECK(!e["info"]["files tree"]["test"]["3-small"].find_key("pieces root"));
auto info = lt::load_torrent_buffer(lt::bencode(e)).ti;
TEST_CHECK(info->layout().root(0_file).is_all_zeros());
TEST_CHECK(!info->layout().root(1_file).is_all_zeros());
}
namespace {
std::string test_create_torrent(std::vector<lt::create_file_entry> fs, int const piece_size
, lt::create_flags_t const flags)
{
lt::create_torrent ct(std::move(fs), piece_size, flags);
ct.set_creation_date(1337);
if (!(flags & lt::create_torrent::v2_only))
for (lt::piece_index_t i : ct.piece_range())
ct.set_hash(i, lt::sha1_hash::max());
if (!(flags & lt::create_torrent::v1_only))
for (auto const f : ct.file_range())
if (!(ct.file_at(f).flags & lt::file_storage::flag_pad_file))
for (auto const p : ct.file_piece_range(f))
ct.set_hash2(f, p, lt::sha256_hash::max());
auto e = ct.generate();
std::string buf;
lt::bencode(std::back_inserter(buf), e);
std::vector<char> const buf2 = lt::bencode(e);
TEST_CHECK(buf2 == ct.generate_buf());
return buf;
}
}
TORRENT_TEST(v1_tail_padding)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files)
, "d13:creation datei1337e4:infod5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi16383e4:pathl7:2-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee");
}
// padding goes before empty files, not after
TORRENT_TEST(v1_empty_file_placement)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-empty", 0);
fs.emplace_back("test/3-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files)
, "d13:creation datei1337e4:infod5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi0e4:pathl7:2-emptyee"
"d6:lengthi16383e4:pathl7:3-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee");
}
// despite the files being added in one order, the torrent is still created with
// files in the canonical order
TORRENT_TEST(v1_file_sorting)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/2-small", 0x3fff);
fs.emplace_back("test/1-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files)
, "d13:creation datei1337e4:infod5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi16383e4:pathl7:2-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee");
}
// This is a backwards compatibility feature
TORRENT_TEST(v1_no_tail_padding)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files_no_tail_padding)
, "d13:creation datei1337e4:infod5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi16383e4:pathl7:2-smallee"
"e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee");
}
// padding goes after empty files in backwards compatibility mode
TORRENT_TEST(v1_empty_file_placement_backwards_compatibility)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-empty", 0);
fs.emplace_back("test/3-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files_no_tail_padding)
, "d13:creation datei1337e4:infod5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d6:lengthi0e4:pathl7:2-emptyee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi16383e4:pathl7:3-smallee"
"e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee");
}
// the v1_only flag does not arrange files canonically (i.e. no ordering nor
// padding)
TORRENT_TEST(v1_no_padding)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only)
, "d13:creation datei1337e4:infod5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d6:lengthi16383e4:pathl7:2-smallee"
"e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee");
}
TORRENT_TEST(hybrid)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, {})
, "d13:creation datei1337e4:infod"
"9:file tree"
"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"7:2-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"e"
"5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi16383e4:pathl7:2-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"e"
"12:meta versioni2e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"e"
"12:piece layersde"
"e");
}
TORRENT_TEST(hybrid_single_file)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("1-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, {})
, "d13:creation datei1337e4:infod"
"9:file tree"
"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"e"
"6:lengthi16383e"
"12:meta versioni2e"
"4:name7:1-small12:piece lengthi16384e"
"6:pieces20:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"e"
"12:piece layersde"
"e");
}
TORRENT_TEST(hybrid_single_file_with_directory)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, {})
, "d13:creation datei1337e4:infod"
"9:file tree"
"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"e"
"5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"e"
"12:meta versioni2e"
"4:name4:test12:piece lengthi16384e"
"6:pieces20:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"e"
"12:piece layersde"
"e");
}
// this is a backwards compatibility feature
TORRENT_TEST(hybrid_no_tail_padding)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/1-small", 0x3fff);
fs.emplace_back("test/2-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::canonical_files_no_tail_padding)
, "d13:creation datei1337e4:infod"
"9:file tree"
"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"7:2-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"e"
"5:filesl"
"d6:lengthi16383e4:pathl7:1-smallee"
"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
"d6:lengthi16383e4:pathl7:2-smallee"
"e"
"12:meta versioni2e"
"4:name4:test12:piece lengthi16384e"
"6:pieces40:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"e"
"12:piece layersde"
"e");
}
TORRENT_TEST(v2_only_file_sorting)
{
std::vector<lt::create_file_entry> fs;
fs.emplace_back("test/2-small", 0x3fff);
fs.emplace_back("test/1-small", 0x3fff);
TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v2_only)
, "d13:creation datei1337e4:infod"
"9:file tree"
"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"7:2-smalld0:d6:lengthi16383e11:pieces root32:"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
"ee"
"e"
"12:meta versioni2e"
"4:name4:test12:piece lengthi16384e"
"e"
"12:piece layersde"
"e");
}
namespace {
std::shared_ptr<lt::torrent_info const>
make_load_torrent(std::vector<lt::create_file_entry> files, int const piece_size)
{
lt::create_torrent ct(std::move(files), piece_size);
for (lt::piece_index_t i : ct.piece_range())
ct.set_hash(i, lt::sha1_hash::max());
auto e = ct.generate();
std::vector<char> const buf = lt::bencode(e);
auto atp = lt::load_torrent_buffer(buf);
return atp.ti;
}
}
// make sure we fill in padding with small files
TORRENT_TEST(canonicalize_pad)
{
std::vector<lt::create_file_entry> files;
files.emplace_back("s/2", 0x7000);
files.emplace_back("s/1", 1);
files.emplace_back("s/3", 0x7001);
auto ti = make_load_torrent(files, 0x4000);
lt::file_storage const& fs = ti->layout();
TEST_EQUAL(fs.num_files(), 6);
TEST_EQUAL(fs.file_size(0_file), 1);
TEST_EQUAL(fs.file_name(0_file), "1");
TEST_EQUAL(fs.pad_file_at(0_file), false);
TEST_EQUAL(fs.file_size(1_file), 0x4000 - 1);
TEST_EQUAL(fs.pad_file_at(1_file), true);
TEST_EQUAL(fs.file_size(2_file), 0x7000);
TEST_EQUAL(fs.file_name(2_file), "2");
TEST_EQUAL(fs.pad_file_at(2_file), false);
TEST_EQUAL(fs.file_size(3_file), 0x8000 - 0x7000);
TEST_EQUAL(fs.pad_file_at(3_file), true);
TEST_EQUAL(fs.file_size(4_file), 0x7001);
TEST_EQUAL(fs.file_name(4_file), "3");
TEST_EQUAL(fs.pad_file_at(4_file), false);
TEST_EQUAL(fs.size_on_disk(), 0x7000 + 1 + 0x7001);
TEST_EQUAL(fs.file_size(5_file), 0x8000 - 0x7001);
TEST_EQUAL(fs.pad_file_at(5_file), true);
}
// make sure canonicalize sorts by path correctly
TORRENT_TEST(canonicalize_path)
{
std::vector<lt::create_file_entry> files;
files.emplace_back("b/2/a", 0x4000);
files.emplace_back("b/1/a", 0x4000);
files.emplace_back("b/3/a", 0x4000);
files.emplace_back("b/11", 0x4000);
auto ti = make_load_torrent(files, 0x4000);
lt::file_storage const& fs = ti->layout();
TEST_EQUAL(fs.num_files(), 4);
using lt::combine_path;
TEST_EQUAL(fs.file_path(0_file), combine_path("b", combine_path("1", "a")));
TEST_EQUAL(fs.file_path(1_file), combine_path("b", "11"));
TEST_EQUAL(fs.file_path(2_file), combine_path("b", combine_path("2", "a")));
TEST_EQUAL(fs.file_path(3_file), combine_path("b", combine_path("3", "a")));
}
TORRENT_TEST(file_num_blocks)
{
std::vector<lt::create_file_entry> files;
files.emplace_back("test/0", 0x5000);
files.emplace_back("test/1", 0x2000);
files.emplace_back("test/2", 0x8000);
files.emplace_back("test/3", 0x8001);
files.emplace_back("test/4", 1);
files.emplace_back("test/5", 0);
auto ti = make_load_torrent(files, 0x8000);
lt::file_storage const& fs = ti->layout();
// generally the number of blocks in a file is:
// (file_size + lt::default_block_size - 1) / lt::default_block_size
TEST_EQUAL(fs.file_num_blocks(0_file), 2);
// pad file at index 1
TEST_CHECK(fs.pad_file_at(1_file));
TEST_EQUAL(fs.file_num_blocks(2_file), 1);
// pad file at index 3
TEST_CHECK(fs.pad_file_at(3_file));
TEST_EQUAL(fs.file_num_blocks(4_file), 2);
TEST_EQUAL(fs.file_num_blocks(5_file), 3);
// pad file at index 6
TEST_CHECK(fs.pad_file_at(6_file));
TEST_EQUAL(fs.file_num_blocks(7_file), 1);
// pad file at index 8
TEST_CHECK(fs.pad_file_at(8_file));
TEST_EQUAL(fs.file_num_blocks(9_file), 0);
}
TORRENT_TEST(file_num_pieces)
{
std::vector<lt::create_file_entry> files;
files.emplace_back("test/0", 0x5000);
files.emplace_back("test/1", 0x2000);
files.emplace_back("test/2", 0x8000);
files.emplace_back("test/3", 0x8001);
files.emplace_back("test/4", 1);
files.emplace_back("test/5", 0);
auto ti = make_load_torrent(files, 0x8000);
lt::file_storage const& fs = ti->layout();
// generally the number of blocks in a file is:
// (file_size + lt::default_block_size - 1) / lt::default_block_size
TEST_EQUAL(fs.file_num_pieces(0_file), 1);
// pad file at index 1
TEST_CHECK(fs.pad_file_at(1_file));
TEST_EQUAL(fs.file_num_pieces(2_file), 1);
// pad file at index 3
TEST_CHECK(fs.pad_file_at(3_file));
TEST_EQUAL(fs.file_num_pieces(4_file), 1);
TEST_EQUAL(fs.file_num_pieces(5_file), 2);
// pad file at index 6
TEST_CHECK(fs.pad_file_at(6_file));
TEST_EQUAL(fs.file_num_pieces(7_file), 1);
// pad file at index 8
TEST_CHECK(fs.pad_file_at(8_file));
TEST_EQUAL(fs.file_num_pieces(9_file), 0);
}
TORRENT_TEST(coalesce_path)
{
using lt::combine_path;
std::vector<lt::create_file_entry> files;
files.emplace_back("test/a", 10000);
files.emplace_back("test/b", 20000);
files.emplace_back("test/c/a", 30000);
files.emplace_back("test/c/b", 40000);
auto ti = make_load_torrent(files, 0x4000);
lt::file_storage const& fs = ti->layout();
// pad files should be created, to make sure the pad files also share the
// same path entries
TEST_EQUAL(fs.paths().size(), 3);
TEST_EQUAL(fs.paths()[0_path], "");
TEST_EQUAL(fs.paths()[1_path], ".pad");
TEST_EQUAL(fs.paths()[2_path], "c");
}
using cfv = lt::aux::vector<lt::create_file_entry, lt::file_index_t>;
#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
#define SEPARATOR "\\"
#else
#define SEPARATOR "/"
#endif
TORRENT_TEST(canonalize_aligned)
{
cfv files;
files.emplace_back("test/1-small", 0x4000);
files.emplace_back("test/2-small", 0x4000);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, false);
TEST_EQUAL(new_files.size(), 2);
TEST_EQUAL(new_files[0_file].filename, "test/1-small");
TEST_EQUAL(new_files[1_file].filename, "test/2-small");
TEST_EQUAL(total, 0x8000);
}
TORRENT_TEST(canonalize_order)
{
cfv files;
files.emplace_back("test/2-small", 0x4000);
files.emplace_back("test/1-small", 0x4000);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, false);
TEST_EQUAL(new_files.size(), 2);
TEST_EQUAL(new_files[0_file].filename, "test/1-small");
TEST_EQUAL(new_files[1_file].filename, "test/2-small");
TEST_EQUAL(total, 0x8000);
}
TORRENT_TEST(canonalize_tail_padding)
{
cfv files;
files.emplace_back("test/1-small", 0x4000);
files.emplace_back("test/2-small", 0x4000);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, false);
TEST_EQUAL(new_files.size(), 4);
TEST_EQUAL(new_files[0_file].filename, "test/1-small");
TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
TEST_EQUAL(new_files[2_file].filename, "test/2-small");
TEST_EQUAL(new_files[3_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
TEST_EQUAL(total, 0x10000);
}
TORRENT_TEST(canonalize_empty_file)
{
cfv files;
files.emplace_back("test/1-small", 0x4000);
files.emplace_back("test/2-empty", 0);
files.emplace_back("test/3-small", 0x4000);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, false);
TEST_EQUAL(new_files.size(), 5);
TEST_EQUAL(new_files[0_file].filename, "test/1-small");
TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
TEST_EQUAL(new_files[2_file].filename, "test/2-empty");
TEST_EQUAL(new_files[3_file].filename, "test/3-small");
TEST_EQUAL(new_files[4_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
TEST_EQUAL(total, 0x10000);
}
TORRENT_TEST(canonalize_single_file)
{
cfv files;
files.emplace_back("test/1-small", 0x4000);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, false);
TEST_EQUAL(new_files.size(), 1);
TEST_EQUAL(new_files[0_file].filename, "test/1-small");
TEST_EQUAL(total, 0x4000);
}
// this is a backwards compatibility feature
TORRENT_TEST(canonalize_no_tail_padding)
{
cfv files;
files.emplace_back("test/1-small", 0x4000);
files.emplace_back("test/2-small", 0x4000);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, true);
TEST_EQUAL(new_files.size(), 3);
TEST_EQUAL(new_files[0_file].filename, "test/1-small");
TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
TEST_EQUAL(new_files[2_file].filename, "test/2-small");
TEST_EQUAL(total, 0xc000);
}
TORRENT_TEST(canonalize_tree)
{
cfv files;
files.emplace_back("test/2/2-small", 0x3fff);
files.emplace_back("test/2/1-small", 0x3fff);
files.emplace_back("test/1/2-small", 0x3fff);
files.emplace_back("test/1/1-small", 0x3fff);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, false);
TEST_EQUAL(new_files.size(), 8);
TEST_EQUAL(new_files[0_file].filename, "test/1/1-small");
TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(new_files[2_file].filename, "test/1/2-small");
TEST_EQUAL(new_files[3_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(new_files[4_file].filename, "test/2/1-small");
TEST_EQUAL(new_files[5_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(new_files[6_file].filename, "test/2/2-small");
TEST_EQUAL(new_files[7_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(total, 0x10000);
}
// this is a backwards compatibility feature
TORRENT_TEST(canonalize_tree_no_tail_padding)
{
cfv files;
files.emplace_back("test/2/2-small", 0x3fff);
files.emplace_back("test/2/1-small", 0x3fff);
files.emplace_back("test/1/2-small", 0x3fff);
files.emplace_back("test/1/1-small", 0x3fff);
auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, true);
TEST_EQUAL(new_files.size(), 7);
TEST_EQUAL(new_files[0_file].filename, "test/1/1-small");
TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(new_files[2_file].filename, "test/1/2-small");
TEST_EQUAL(new_files[3_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(new_files[4_file].filename, "test/2/1-small");
TEST_EQUAL(new_files[5_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
TEST_EQUAL(new_files[6_file].filename, "test/2/2-small");
TEST_EQUAL(total, 0x10000 - 1);
}