factor out parse_tracker_response and add unit tests. make gen_todo cover tests also, and regenerate todo.html
This commit is contained in:
parent
1c915f2e95
commit
2d438e0758
|
@ -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))
|
||||
|
||||
|
|
1626
docs/todo.html
1626
docs/todo.html
File diff suppressed because one or more lines are too long
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
cb->tracker_response(tracker_req(), m_tracker_ip, ip_list, resp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue