Files

322 lines
7.9 KiB
C++

/*
Copyright (c) 2022, Arvid Norberg
All rights reserved.
You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/
#include "simulator/simulator.hpp"
#include "simulator/utils.hpp"
#include "test.hpp"
#include "create_torrent.hpp"
#include "settings.hpp"
#include "setup_swarm.hpp"
#include "utils.hpp"
#include "test_utils.hpp"
#include "setup_transfer.hpp" // for addr()
#include "disk_io.hpp"
#include "libtorrent/add_torrent_params.hpp"
#include "libtorrent/alert_types.hpp"
namespace {
template <typename Setup, typename HandleAlerts>
void run_test(
Setup setup
, HandleAlerts on_alert
, test_disk const downloader_disk_constructor = test_disk()
, test_disk const seed_disk_constructor = test_disk()
)
{
char const* peer0_ip = "50.0.0.1";
char const* peer1_ip = "50.0.0.2";
lt::address peer0 = addr(peer0_ip);
lt::address peer1 = addr(peer1_ip);
// setup the simulation
sim::default_config network_cfg;
sim::simulation sim{network_cfg};
sim::asio::io_context ios0 { sim, peer0 };
sim::asio::io_context ios1 { sim, peer1 };
lt::session_proxy zombie[2];
lt::session_params params;
// setup settings pack to use for the session (customization point)
lt::settings_pack& pack = params.settings;
pack = settings();
pack.set_str(lt::settings_pack::listen_interfaces, make_ep_string(peer0_ip, false, "6881"));
// create session
std::shared_ptr<lt::session> ses[2];
// session 0 is a downloader, session 1 is a seed
params.disk_io_constructor = downloader_disk_constructor;
ses[0] = std::make_shared<lt::session>(params, ios0);
pack.set_str(lt::settings_pack::listen_interfaces, make_ep_string(peer1_ip, false, "6881"));
params.disk_io_constructor = seed_disk_constructor.set_files(existing_files_mode::full_valid);
ses[1] = std::make_shared<lt::session>(params, ios1);
setup(*ses[0], *ses[1]);
// only monitor alerts for session 0 (the downloader)
print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) {
if (auto ta = lt::alert_cast<lt::add_torrent_alert>(a))
ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881));
on_alert(ses, a);
}, 0);
print_alerts(*ses[1], [](lt::session&, lt::alert const*){}, 1);
// the min reconnect time defaults to 60 seconds
sim::timer t(sim, lt::seconds(70), [&](boost::system::error_code const&)
{
// shut down
int idx = 0;
for (auto& s : ses)
{
zombie[idx++] = s->abort();
s.reset();
}
});
sim.run();
}
lt::info_hash_t setup_conflict(lt::session& seed, lt::session& downloader)
{
lt::add_torrent_params atp = ::create_test_torrent(10, lt::create_flags_t{}, 2);
atp.flags &= ~lt::torrent_flags::auto_managed;
atp.flags &= ~lt::torrent_flags::paused;
// add the complete torrent to the seed
seed.async_add_torrent(atp);
lt::info_hash_t const ih = atp.ti->info_hashes();
// add v1-only magnet link
atp.ti.reset();
atp.info_hashes.v1 = ih.v1;
atp.info_hashes.v2.clear();
downloader.async_add_torrent(atp);
// add v2-only magnet link
atp.info_hashes.v1.clear();
atp.info_hashes.v2 = ih.v2;
downloader.async_add_torrent(atp);
return ih;
}
} // anonymous namespace
// This adds the same hybrid torrent twice, once via the v1 info-hash and once
// via the v2 info-hash. Once the conflict is detected, both torrents should
// fail with the duplicate_torrent error state.
TORRENT_TEST(hybrid_torrent_conflict)
{
std::vector<lt::torrent_handle> handles;
int errors = 0;
int conflict = 0;
lt::info_hash_t added_ih;
run_test([&](lt::session& ses0, lt::session& ses1) {
added_ih = setup_conflict(ses1, ses0);
},
[&](lt::session& ses, lt::alert const* a) {
if (auto const* ta = lt::alert_cast<lt::add_torrent_alert>(a))
{
handles.push_back(ta->handle);
}
else if (lt::alert_cast<lt::torrent_removed_alert>(a))
{
TEST_ERROR("a torrent was removed");
}
else if (auto const* te = lt::alert_cast<lt::torrent_error_alert>(a))
{
++errors;
// both handles are expected to fail with duplicate torrent error
TEST_EQUAL(te->error, lt::error_code(lt::errors::duplicate_torrent));
}
else if (auto const* tc = lt::alert_cast<lt::torrent_conflict_alert>(a))
{
++conflict;
TEST_EQUAL(std::count(handles.begin(), handles.end(), tc->handle), 1);
TEST_EQUAL(std::count(handles.begin(), handles.end(), tc->conflicting_torrent), 1);
TEST_CHECK(tc->handle != tc->conflicting_torrent);
TEST_CHECK(added_ih == tc->metadata->info_hashes());
}
for (auto& h : handles)
TEST_CHECK(h.is_valid());
auto torrents = ses.get_torrents();
if (handles.size() == 2)
{
TEST_EQUAL(torrents.size(), 2);
}
}
);
TEST_EQUAL(errors, 2);
TEST_EQUAL(conflict, 1);
}
// try to resume the torrents after failing with a conflict. Ensure they both
// fail again with the same error
TORRENT_TEST(resume_conflict)
{
std::vector<lt::torrent_handle> handles;
int errors = 0;
int resume = 0;
run_test([](lt::session& ses0, lt::session& ses1) {
setup_conflict(ses1, ses0);
},
[&](lt::session& ses, lt::alert const* a) {
if (auto const* ta = lt::alert_cast<lt::add_torrent_alert>(a))
{
handles.push_back(ta->handle);
}
else if (lt::alert_cast<lt::torrent_removed_alert>(a))
{
TEST_ERROR("a torrent was removed");
}
else if (auto const* te = lt::alert_cast<lt::torrent_error_alert>(a))
{
++errors;
// both handles are expected to fail with duplicate torrent error
TEST_EQUAL(te->error, lt::error_code(lt::errors::duplicate_torrent));
if (resume < 2)
{
te->handle.clear_error();
te->handle.resume();
++resume;
}
}
for (auto& h : handles)
TEST_CHECK(h.is_valid());
auto torrents = ses.get_torrents();
if (handles.size() == 2)
{
TEST_EQUAL(torrents.size(), 2);
}
}
);
TEST_EQUAL(errors, 4);
TEST_EQUAL(resume, 2);
}
TORRENT_TEST(resolve_conflict)
{
int errors = 0;
int finished = 0;
int removed = 0;
run_test([](lt::session& ses0, lt::session& ses1) {
setup_conflict(ses1, ses0);
},
[&](lt::session& ses, lt::alert const* a) {
if (lt::alert_cast<lt::torrent_removed_alert>(a))
{
++removed;
}
else if (auto const* te = lt::alert_cast<lt::torrent_error_alert>(a))
{
++errors;
// both handles are expected to fail with duplicate torrent error
TEST_EQUAL(te->error, lt::error_code(lt::errors::duplicate_torrent));
if (errors == 1)
{
ses.remove_torrent(te->handle);
}
else if (errors == 2)
{
te->handle.clear_error();
te->handle.resume();
}
}
else if (lt::alert_cast<lt::torrent_finished_alert>(a))
{
++finished;
}
if (errors == 2)
{
auto torrents = ses.get_torrents();
TEST_EQUAL(torrents.size(), 1);
}
});
TEST_EQUAL(errors, 2);
TEST_EQUAL(finished, 1);
TEST_EQUAL(removed, 1);
}
TORRENT_TEST(conflict_readd)
{
std::vector<lt::torrent_handle> handles;
int errors = 0;
int finished = 0;
int removed = 0;
int conflict = 0;
run_test([](lt::session& ses0, lt::session& ses1) {
setup_conflict(ses1, ses0);
},
[&](lt::session& ses, lt::alert const* a) {
if (auto const* ta = lt::alert_cast<lt::add_torrent_alert>(a))
{
handles.push_back(ta->handle);
}
else if (lt::alert_cast<lt::torrent_removed_alert>(a))
{
++removed;
}
else if (auto const* te = lt::alert_cast<lt::torrent_error_alert>(a))
{
++errors;
// both handles are expected to fail with duplicate torrent error
TEST_EQUAL(te->error, lt::error_code(lt::errors::duplicate_torrent));
}
else if (auto const* tf = lt::alert_cast<lt::torrent_finished_alert>(a))
{
++finished;
TEST_EQUAL(handles.size(), 1);
TEST_CHECK(handles[0] == tf->handle);
}
else if (auto const* tc = lt::alert_cast<lt::torrent_conflict_alert>(a))
{
++conflict;
ses.remove_torrent(tc->handle);
ses.remove_torrent(tc->conflicting_torrent);
handles.clear();
lt::add_torrent_params atp;
atp.ti = std::move(tc->metadata);
atp.save_path = ".";
ses.async_add_torrent(std::move(atp));
}
for (auto& h : handles)
TEST_CHECK(h.is_valid());
});
TEST_EQUAL(errors, 2);
TEST_EQUAL(finished, 1);
TEST_EQUAL(removed, 2);
TEST_EQUAL(conflict, 1);
}