factor out parse_tracker_response and add unit tests. make gen_todo cover tests also, and regenerate todo.html

This commit is contained in:
Arvid Norberg 2014-09-29 06:10:22 +00:00
parent 1c915f2e95
commit 2d438e0758
7 changed files with 1506 additions and 540 deletions

View File

@ -1,7 +1,7 @@
import glob
import os
paths = ['src/*.cpp', 'src/kademlia/*.cpp', 'include/libtorrent/*.hpp', 'include/libtorrent/kademlia/*.hpp', 'include/libtorrent/aux_/*.hpp', 'include/libtorrent/extensions/*.hpp']
paths = ['test/*.cpp', 'src/*.cpp', 'src/kademlia/*.cpp', 'include/libtorrent/*.hpp', 'include/libtorrent/kademlia/*.hpp', 'include/libtorrent/aux_/*.hpp', 'include/libtorrent/extensions/*.hpp']
os.system('(cd .. ; ctags %s 2>/dev/null)' % ' '.join(paths))

File diff suppressed because one or more lines are too long

View File

@ -95,9 +95,6 @@ namespace libtorrent
virtual void on_timeout(error_code const&) {}
void parse(int status_code, lazy_entry const& e);
bool extract_peer_info(lazy_entry const& e, peer_entry& ret);
tracker_manager& m_man;
boost::shared_ptr<http_connection> m_tracker_connection;
aux::session_impl& m_ses;
@ -109,6 +106,9 @@ namespace libtorrent
#endif
};
TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response(
char const* data, int size, error_code& ec
, bool scrape_request, sha1_hash scrape_ih);
}
#endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED

View File

@ -237,7 +237,8 @@ namespace libtorrent
// if this is a dictionary, look for a key ``name`` whose value
// is an int. If such key exist, return a pointer to its value,
// otherwise NULL.
boost::int64_t dict_find_int_value(char const* name, boost::int64_t default_val = 0) const;
boost::int64_t dict_find_int_value(char const* name
, boost::int64_t default_val = 0) const;
lazy_entry const* dict_find_int(char const* name) const;
// these functions require that ``this`` is a dictionary.

View File

@ -150,20 +150,48 @@ namespace libtorrent
, min_interval(120)
, complete(-1)
, incomplete(-1)
, downloaders(-1)
, downloaded(-1)
{}
// peers from the tracker, in various forms
std::vector<peer_entry> peers;
std::vector<ipv4_peer_entry> peers4;
#if TORRENT_USE_IPV6
std::vector<ipv6_peer_entry> peers6;
#endif
// our external IP address (if the tracker responded with ti, otherwise
// INADDR_ANY)
address external_ip;
// the tracker id, if it was included in the response, otherwise
// an empty string
std::string trackerid;
// if the tracker returned an error, this is set to that error
std::string failure_reason;
// contains a warning message from the tracker, if included in
// the response
std::string warning_message;
// re-announce interval, in seconds
int interval;
// the lowest force-announce interval
int min_interval;
// the number of seeds in the swarm
int complete;
// the number of downloaders in the swarm
int incomplete;
// if supported by the tracker, the number of actively downloading peers.
// i.e. partial seeds. If not suppored, -1
int downloaders;
// the number of times the torrent has been downloaded
int downloaded;
};

View File

@ -328,27 +328,70 @@ namespace libtorrent
received_bytes(size + parser.body_start());
// handle tracker response
lazy_entry e;
error_code ecode;
int res = lazy_bdecode(data, data + size, e, ecode);
if (res == 0 && e.type() == lazy_entry::dict_t)
boost::shared_ptr<request_callback> cb = requester();
if (!cb)
{
parse(parser.status_code(), e);
close();
return;
}
tracker_response resp = parse_tracker_response(data, size, ecode
, tracker_req().kind == tracker_request::scrape_request
, tracker_req().info_hash);
if (!resp.warning_message.empty())
cb->tracker_warning(tracker_req(), resp.warning_message);
if (ecode)
{
fail(ecode, parser.status_code());
close();
return;
}
if (!resp.failure_reason.empty())
{
fail(error_code(errors::tracker_failure), parser.status_code()
, resp.failure_reason.c_str(), resp.interval, resp.min_interval);
close();
return;
}
// do slightly different things for scrape requests
if (tracker_req().kind == tracker_request::scrape_request)
{
cb->tracker_scrape_response(tracker_req(), resp.complete
, resp.incomplete, resp.downloaded, resp.downloaders);
}
else
{
fail(ecode, parser.status_code());
std::list<address> ip_list;
if (m_tracker_connection)
{
error_code ec;
ip_list.push_back(
m_tracker_connection->socket().remote_endpoint(ec).address());
std::vector<tcp::endpoint> const& epts = m_tracker_connection->endpoints();
for (std::vector<tcp::endpoint>::const_iterator i = epts.begin()
, end(epts.end()); i != end; ++i)
{
ip_list.push_back(i->address());
}
}
cb->tracker_response(tracker_req(), m_tracker_ip, ip_list, resp);
}
close();
}
bool http_tracker_connection::extract_peer_info(lazy_entry const& info, peer_entry& ret)
bool extract_peer_info(lazy_entry const& info, peer_entry& ret, error_code& ec)
{
// extract peer id (if any)
if (info.type() != lazy_entry::dict_t)
{
fail(error_code(errors::invalid_peer_dict));
ec.assign(errors::invalid_peer_dict, get_libtorrent_category());
return false;
}
lazy_entry const* i = info.dict_find_string("peer id");
@ -366,7 +409,7 @@ namespace libtorrent
i = info.dict_find_string("ip");
if (i == 0)
{
fail(error_code(errors::invalid_tracker_response));
ec.assign(errors::invalid_tracker_response, get_libtorrent_category());
return false;
}
ret.hostname = i->string_value();
@ -375,7 +418,7 @@ namespace libtorrent
i = info.dict_find_int("port");
if (i == 0)
{
fail(error_code(errors::invalid_tracker_response));
ec.assign(errors::invalid_tracker_response, get_libtorrent_category());
return false;
}
ret.port = (unsigned short)i->int_value();
@ -383,18 +426,27 @@ namespace libtorrent
return true;
}
// TODO: 2 make this a free function that can be easily unit tested
void http_tracker_connection::parse(int status_code, lazy_entry const& e)
tracker_response parse_tracker_response(char const* data, int size, error_code& ec
, bool scrape_request, sha1_hash scrape_ih)
{
boost::shared_ptr<request_callback> cb = requester();
if (!cb) return;
tracker_response resp;
lazy_entry e;
int res = lazy_bdecode(data, data + size, e, ec);
if (ec) return resp;
if (res != 0 || e.type() != lazy_entry::dict_t)
{
ec.assign(errors::invalid_tracker_response, get_libtorrent_category());
return resp;
}
int interval = int(e.dict_find_int_value("interval", 0));
// if no interval is specified, default to 30 minutes
if (interval == 0) interval = 1800;
int min_interval = int(e.dict_find_int_value("min interval", 30));
tracker_response resp;
resp.interval = interval;
resp.min_interval = min_interval;
@ -406,44 +458,48 @@ namespace libtorrent
lazy_entry const* failure = e.dict_find_string("failure reason");
if (failure)
{
fail(error_code(errors::tracker_failure), status_code
, failure->string_value().c_str(), interval, min_interval);
return;
resp.failure_reason = failure->string_value();
ec.assign(errors::tracker_failure, get_libtorrent_category());
return resp;
}
lazy_entry const* warning = e.dict_find_string("warning message");
if (warning)
cb->tracker_warning(tracker_req(), warning->string_value());
resp.warning_message = warning->string_value();
if (tracker_req().kind == tracker_request::scrape_request)
if (scrape_request)
{
std::string ih = tracker_req().info_hash.to_string();
lazy_entry const* files = e.dict_find_dict("files");
if (files == 0)
{
fail(error_code(errors::invalid_files_entry), -1, ""
, interval, min_interval);
return;
ec.assign(errors::invalid_files_entry, get_libtorrent_category());
return resp;
}
lazy_entry const* scrape_data = files->dict_find_dict(ih.c_str());
// TODO: 4 this is a bug. if the info-hash contains a 0, this will
// fail!
lazy_entry const* scrape_data = files->dict_find_dict(
scrape_ih.to_string().c_str());
if (scrape_data == 0)
{
fail(error_code(errors::invalid_hash_entry), -1, ""
, interval, min_interval);
return;
ec.assign(errors::invalid_hash_entry, get_libtorrent_category());
return resp;
}
int complete = int(scrape_data->dict_find_int_value("complete", -1));
int incomplete = int(scrape_data->dict_find_int_value("incomplete", -1));
int downloaded = int(scrape_data->dict_find_int_value("downloaded", -1));
int downloaders = int(scrape_data->dict_find_int_value("downloaders", -1));
cb->tracker_scrape_response(tracker_req(), complete
, incomplete, downloaded, downloaders);
return;
resp.complete = int(scrape_data->dict_find_int_value("complete", -1));
resp.incomplete = int(scrape_data->dict_find_int_value("incomplete", -1));
resp.downloaded = int(scrape_data->dict_find_int_value("downloaded", -1));
resp.downloaders = int(scrape_data->dict_find_int_value("downloaders", -1));
return resp;
}
// look for optional scrape info
resp.complete = int(e.dict_find_int_value("complete", -1));
resp.incomplete = int(e.dict_find_int_value("incomplete", -1));
resp.downloaded = int(e.dict_find_int_value("downloaded", -1));
lazy_entry const* peers_ent = e.dict_find("peers");
if (peers_ent && peers_ent->type() == lazy_entry::string_t)
{
@ -465,12 +521,21 @@ namespace libtorrent
{
int len = peers_ent->list_size();
resp.peers.reserve(len);
error_code parse_error;
for (int i = 0; i < len; ++i)
{
peer_entry p;
if (!extract_peer_info(*peers_ent->list_at(i), p)) return;
if (!extract_peer_info(*peers_ent->list_at(i), p, parse_error))
continue;
resp.peers.push_back(p);
}
// only report an error if all peer entries are invalid
if (resp.peers.empty() && parse_error)
{
ec = parse_error;
return resp;
}
}
else
{
@ -501,16 +566,15 @@ namespace libtorrent
#else
lazy_entry const* ipv6_peers = 0;
#endif
/*
// if we didn't receive any peers. We don't care if we're stopping anyway
if (peers_ent == 0 && ipv6_peers == 0
&& tracker_req().event != tracker_request::stopped)
{
fail(error_code(errors::invalid_peers_entry), -1, ""
, interval, min_interval);
return;
ec.assign(errors::invalid_peers_entry, get_libtorrent_category());
return resp;
}
*/
lazy_entry const* ip_ent = e.dict_find_string("external ip");
if (ip_ent)
{
@ -523,26 +587,7 @@ namespace libtorrent
#endif
}
// look for optional scrape info
resp.complete = int(e.dict_find_int_value("complete", -1));
resp.incomplete = int(e.dict_find_int_value("incomplete", -1));
resp.downloaded = int(e.dict_find_int_value("downloaded", -1));
std::list<address> ip_list;
if (m_tracker_connection)
{
error_code ec;
ip_list.push_back(m_tracker_connection->socket().remote_endpoint(ec).address());
std::vector<tcp::endpoint> const& epts = m_tracker_connection->endpoints();
for (std::vector<tcp::endpoint>::const_iterator i = epts.begin()
, end(epts.end()); i != end; ++i)
{
ip_list.push_back(i->address());
}
}
cb->tracker_response(tracker_req(), m_tracker_ip, ip_list, resp);
return resp;
}
}

View File

@ -36,14 +36,176 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/alert.hpp"
#include "libtorrent/session.hpp"
#include "libtorrent/error_code.hpp"
#include "libtorrent/tracker_manager.hpp"
#include "libtorrent/http_tracker_connection.hpp" // for parse_tracker_response
#include <fstream>
using namespace libtorrent;
namespace lt = libtorrent;
void test_parse_hostname_peers()
{
char const response[] = "d5:peersld7:peer id20:aaaaaaaaaaaaaaaaaaaa2:ip13:test_hostname4:porti1000eed7:peer id20:bbbbabaababababababa2:ip12:another_host4:porti1001eeee";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.peers.size(), 2);
if (resp.peers.size() == 2)
{
peer_entry const& e0 = resp.peers[0];
peer_entry const& e1 = resp.peers[1];
TEST_EQUAL(e0.hostname, "test_hostname");
TEST_EQUAL(e0.port, 1000);
TEST_EQUAL(e0.pid, peer_id("aaaaaaaaaaaaaaaaaaaa"));
TEST_EQUAL(e1.hostname, "another_host");
TEST_EQUAL(e1.port, 1001);
TEST_EQUAL(e1.pid, peer_id("bbbbabaababababababa"));
}
}
void test_parse_peers4()
{
char const response[] = "d5:peers12:\x01\x02\x03\x04\x30\x10"
"\x09\x08\x07\x06\x20\x10" "e";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.peers4.size(), 2);
if (resp.peers.size() == 2)
{
ipv4_peer_entry const& e0 = resp.peers4[0];
ipv4_peer_entry const& e1 = resp.peers4[1];
TEST_CHECK(e0.ip == address_v4::from_string("1.2.3.4").to_bytes());
TEST_EQUAL(e0.port, 0x3010);
TEST_CHECK(e1.ip == address_v4::from_string("9.8.7.6").to_bytes());
TEST_EQUAL(e1.port, 0x2010);
}
}
void test_parse_interval()
{
char const response[] = "d8:intervali1042e12:min intervali10e5:peers0:e";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.peers.size(), 0);
TEST_EQUAL(resp.peers4.size(), 0);
TEST_EQUAL(resp.interval, 1042);
TEST_EQUAL(resp.min_interval, 10);
}
void test_parse_warning()
{
char const response[] = "d5:peers0:15:warning message12:test messagee";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.peers.size(), 0);
TEST_EQUAL(resp.warning_message, "test message");
}
void test_parse_failure_reason()
{
char const response[] = "d5:peers0:14:failure reason12:test messagee";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code(errors::tracker_failure));
TEST_EQUAL(resp.peers.size(), 0);
TEST_EQUAL(resp.failure_reason, "test message");
}
void test_parse_scrape_response()
{
char const response[] = "d5:filesd20:aaaaaaaaaaaaaaaaaaaad8:completei1e10:incompletei2e10:downloadedi3e11:downloadersi6eeee";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, true, sha1_hash("aaaaaaaaaaaaaaaaaaaa"));
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.complete, 1);
TEST_EQUAL(resp.incomplete, 2);
TEST_EQUAL(resp.downloaded, 3);
TEST_EQUAL(resp.downloaders, 6);
}
void test_parse_scrape_response_with_zero()
{
char const response[] = "d5:filesd20:aaa\0aaaaaaaaaaaaaaaad8:completei4e10:incompletei5e10:downloadedi6eeee";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, true, sha1_hash("aaa\0aaaaaaaaaaaaaaaa"));
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.complete, 1);
TEST_EQUAL(resp.incomplete, 2);
TEST_EQUAL(resp.downloaded, 3);
TEST_EQUAL(resp.downloaders, -1);
}
void test_parse_external_ip()
{
char const response[] = "d5:peers0:11:external ip4:\x01\x02\x03\x04" "e";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.peers.size(), 0);
TEST_EQUAL(resp.external_ip, address_v4::from_string("1.2.3.4"));
}
#if TORRENT_USE_IPV6
void test_parse_external_ip6()
{
char const response[] = "d5:peers0:11:external ip16:\xf1\x02\x03\x04\0\0\0\0\0\0\0\0\0\0\xff\xff" "e";
error_code ec;
tracker_response resp = parse_tracker_response(response, sizeof(response) - 1
, ec, false, sha1_hash());
TEST_EQUAL(ec, error_code());
TEST_EQUAL(resp.peers.size(), 0);
TEST_EQUAL(resp.external_ip, address_v6::from_string("f102:0304::ffff"));
}
#endif
int test_main()
{
test_parse_hostname_peers();
test_parse_peers4();
test_parse_interval();
test_parse_warning();
test_parse_failure_reason();
test_parse_scrape_response();
test_parse_scrape_response_with_zero();
test_parse_external_ip();
#if TORRENT_USE_IPV6
test_parse_external_ip6();
#endif
// TODO: test parse peers6
// TODO: test parse tracker-id
// TODO: test parse failure-reason
// TODO: test all failure paths
// invalid bencoding
// not a dictionary
// no files entry in scrape response
// no info-hash entry in scrape response
// malformed peers in peer list of dictionaries
// uneven number of bytes in peers and peers6 string responses
int http_port = start_web_server();
int udp_port = start_udp_tracker();