rink.nu / projects / tortilla / lib/trackertalker.cc@89367a44e2dc (annotated)
lib/trackertalker.cc
author Rink Springer <rink@rink.nu>
Sun Jul 31 12:34:24 2011 +0200 (2011-07-31 ago)
changeset 328 89367a44e2dc
parent 319 99568dedb218
permissions -rw-r--r--
When parsing trackers, revert to the old-style 'announce' URL if 'announce-list' is corrupt

I've seen one torrent which has an empty 'announce-list', causing it to be rejected completely. This may be overly strict, even though the presence of of an empty 'announce-list' should be considered a bug.
rink@304
     1
#include <boost/thread/locks.hpp>
rink@270
     2
#include <algorithm>
rink@270
     3
#include "trackertalker.h"
rink@270
     4
#include "macros.h"
rink@270
     5
#include "metafield.h"
rink@270
     6
#include "httprequest.h"
rink@270
     7
#include "exceptions.h"
rink@270
     8
#include "tracer.h"
rink@270
     9
rink@270
    10
using namespace std;
rink@304
    11
using namespace boost;
rink@319
    12
using namespace Tortilla;
rink@270
    13
rink@270
    14
#define TRACER (getTracer())
rink@270
    15
rink@307
    16
AnnounceTier::AnnounceTier(TrackerTalker* tt, const MetaList* ml)
rink@270
    17
{
rink@270
    18
	talker = tt;
rink@307
    19
	for (list<MetaField*>::const_iterator it = ml->getList().begin();
rink@270
    20
		   it != ml->getList().end(); it++) {
rink@270
    21
		MetaString* ms = dynamic_cast<MetaString*>(*it);
rink@270
    22
		if (ms == NULL)
rink@270
    23
			throw TrackerException("announce tear list doesn't contain a string");
rink@270
    24
		string trackerURL = ms->getString();
rink@270
    25
		trackers.push_back(trackerURL);
rink@270
    26
		TRACE(TRACKER, "added tracker: %s", trackerURL.c_str());
rink@270
    27
	}
rink@270
    28
	random_shuffle(trackers.begin(), trackers.end());
rink@270
    29
}
rink@270
    30
rink@270
    31
AnnounceTier::AnnounceTier(TrackerTalker* tt, std::string url)
rink@270
    32
{
rink@270
    33
	talker = tt;
rink@270
    34
	trackers.push_back(url);
rink@270
    35
}
rink@270
    36
rink@270
    37
void
rink@270
    38
AnnounceTier::resetCurrentTracker()
rink@270
    39
{
rink@270
    40
	currentTracker = 0;
rink@270
    41
}
rink@270
    42
rink@270
    43
string
rink@270
    44
AnnounceTier::getNextTracker()
rink@270
    45
{
rink@270
    46
	if (currentTracker >= trackers.size())
rink@270
    47
		return "";
rink@270
    48
	return trackers[currentTracker++];
rink@270
    49
}
rink@270
    50
rink@270
    51
void
rink@270
    52
AnnounceTier::demoteCurrentTracker()
rink@270
    53
{
rink@270
    54
	TRACE(TRACKER, "TODO: demote");
rink@270
    55
}
rink@270
    56
rink@270
    57
void
rink@270
    58
AnnounceTier::promoteCurrentTracker()
rink@270
    59
{
rink@270
    60
	TRACE(TRACKER, "TODO: promote");
rink@270
    61
}
rink@270
    62
rink@270
    63
Tracer*
rink@270
    64
AnnounceTier::getTracer() {
rink@270
    65
	return talker->getTorrent()->getTracer();
rink@270
    66
}
rink@270
    67
rink@270
    68
TrackerTalker::TrackerTalker(Torrent* t, MetaDictionary* dictionary)
rink@270
    69
{
rink@270
    70
	torrent = t; httpRequest = NULL;
rink@270
    71
rink@307
    72
	const MetaList* mlList = dynamic_cast<const MetaList*>((*dictionary)["announce-list"]);
rink@328
    73
	if (mlList != NULL) {
rink@328
    74
		/*
rink@328
    75
		 * An announce-list is made up of lists of sublists. Each sublist is an
rink@328
    76
		 * announcement tier, and contains a list of strings which must be shuffeled
rink@328
    77
		 * and stored. Each of the tiers must be tried in-order.
rink@328
    78
		 */
rink@328
    79
		try {
rink@328
    80
			for (list<MetaField*>::const_iterator it = mlList->getList().begin();
rink@328
    81
					 it != mlList->getList().end(); it++) {
rink@328
    82
					MetaList* tierList = dynamic_cast<MetaList*>(*it);
rink@328
    83
					if (tierList == NULL)
rink@328
    84
						throw TorrentException("announce list doesn't contain a list element");
rink@270
    85
rink@328
    86
					tiers.push_back(new AnnounceTier(this, tierList));
rink@328
    87
			}
rink@328
    88
		} catch (TortillaException) {
rink@328
    89
			/*
rink@328
    90
			 * Something went wrong while adding tiers; throw them all away and
rink@328
    91
			 * attempt to add the old-style single URL.
rink@328
    92
			 */
rink@328
    93
			for (vector<AnnounceTier*>::iterator it = tiers.begin();
rink@328
    94
					 it != tiers.end(); it++) {
rink@328
    95
				delete (*it);
rink@328
    96
			}
rink@328
    97
			tiers.clear();
rink@328
    98
		}
rink@270
    99
	}
rink@270
   100
rink@328
   101
	/* If we have an announce list, we're good to go */
rink@328
   102
	if (!tiers.empty())
rink@328
   103
		return;
rink@270
   104
rink@328
   105
	/* New-style announce list unavailable or corrupt; revert to old single URL */
rink@328
   106
	const MetaString* msAnnounce = dynamic_cast<const MetaString*>((*dictionary)["announce"]);
rink@328
   107
	if (msAnnounce == NULL)
rink@328
   108
		throw TrackerException("metadata doesn't contain an announce URL or list");
rink@328
   109
rink@328
   110
	string url = msAnnounce->getString();
rink@328
   111
	TRACE(TRACKER, "added old-style tracker: %s", url.c_str());
rink@328
   112
	tiers.push_back(new AnnounceTier(this, url));
rink@270
   113
}
rink@270
   114
rink@270
   115
TrackerTalker::~TrackerTalker()
rink@270
   116
{
rink@270
   117
	for (vector<AnnounceTier*>::iterator it = tiers.begin();
rink@270
   118
	     it != tiers.end(); it++) {
rink@270
   119
		delete (*it);
rink@270
   120
	}
rink@270
   121
}
rink@270
   122
rink@270
   123
void
rink@270
   124
TrackerTalker::request(map<string, string> req)
rink@270
   125
{
rink@270
   126
	/* Always start at the first tier with this request */
rink@270
   127
	currentTier = 0; currentRequest = req;
rink@270
   128
	for (vector<AnnounceTier*>::iterator it = tiers.begin();
rink@270
   129
	     it != tiers.end(); it++) {
rink@270
   130
		(*it)->resetCurrentTracker();
rink@270
   131
	}
rink@270
   132
rink@270
   133
	/* Give it a spin */
rink@270
   134
	if (!tryRequest()) {
rink@270
   135
		/* Wow, this failed extremely quickly - talk to the torrent */
rink@270
   136
		torrent->callbackTrackerReply("", true);
rink@270
   137
	}
rink@270
   138
}
rink@270
   139
rink@270
   140
bool
rink@270
   141
TrackerTalker::tryRequest()
rink@270
   142
{
rink@304
   143
	unique_lock<mutex> lock(mtx_data);
rink@270
   144
	while (true) {
rink@270
   145
		/* Find the next announcer to use */
rink@270
   146
		if (currentTier >= tiers.size())
rink@270
   147
			break;
rink@270
   148
		AnnounceTier* at = tiers[currentTier];
rink@270
   149
		string url = at->getNextTracker();
rink@270
   150
		if (url == "") {
rink@270
   151
			currentTier++;
rink@270
   152
			continue;
rink@270
   153
		}
rink@270
   154
rink@270
   155
		/* OK, we got an URL - try to chat with it */
rink@304
   156
		lock.unlock(); /* drop data lock while talking to tracker */
rink@270
   157
		try {
rink@270
   158
			TRACE(TRACKER, "attempting to contact tracker '%s'", url.c_str());
rink@270
   159
			httpRequest = new HTTPRequest(this, url, currentRequest);
rink@270
   160
			torrent->addRequest(httpRequest);
rink@270
   161
			return true;
rink@270
   162
		} catch (HTTPException e) {
rink@270
   163
			TRACE(TRACKER, "unable to communicate with tracker '%s': %s", url.c_str(), e.what());
rink@270
   164
		}
rink@270
   165
rink@304
   166
		/* Fallthrough to the next tracker - reacquire lock */
rink@304
   167
		lock.lock();
rink@270
   168
	}
rink@270
   169
	return false;
rink@270
   170
}
rink@270
   171
rink@270
   172
void 
rink@270
   173
TrackerTalker::callbackTrackerRequest(std::string result, bool error)
rink@270
   174
{
rink@270
   175
	TRACE(TRACKER, "tracker callback: error=%u", error ? 1 : 0);
rink@270
   176
rink@270
   177
	/* Get rid of the request ASAP - the receiver will clean it up */
rink@304
   178
	{
rink@304
   179
		unique_lock<mutex> lock(mtx_data);
rink@304
   180
		httpRequest = NULL;
rink@304
   181
	}
rink@270
   182
rink@270
   183
	AnnounceTier* at = tiers[currentTier];
rink@270
   184
rink@270
   185
	if (error) {
rink@270
   186
		at->demoteCurrentTracker();
rink@270
   187
		if (!tryRequest()) {
rink@270
   188
			/* Give up... */
rink@270
   189
			torrent->callbackTrackerReply(result, true);
rink@270
   190
		}
rink@270
   191
		return;
rink@270
   192
	}
rink@270
   193
rink@270
   194
	at->promoteCurrentTracker();
rink@270
   195
	torrent->callbackTrackerReply(result, false);
rink@270
   196
}
rink@270
   197
rink@270
   198
/* vim:set ts=2 sw=2: */
Powered by FreeBSD, PostgreSQL and Perl
© 2001 - 2014 Rink Springer