source: trunk/libtransmission/tracker.c @ 6613

Last change on this file since 6613 was 6613, checked in by charles, 13 years ago

more metainfo/bencode cleanup

  • Property svn:keywords set to Date Rev Author Id
File size: 28.9 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: tracker.c 6613 2008-08-21 16:12:17Z charles $
11 */
12
13#include <assert.h>
14#include <stdlib.h>
15#include <string.h> /* strcmp, strchr */
16
17#include <event.h>
18
19#include "transmission.h"
20#include "bencode.h"
21#include "crypto.h"
22#include "completion.h"
23#include "net.h"
24#include "publish.h"
25#include "resume.h"
26#include "torrent.h"
27#include "tracker.h"
28#include "trevent.h"
29#include "utils.h"
30#include "web.h"
31
32enum
33{
34    HTTP_OK = 200,
35
36    /* seconds between tracker pulses */
37    PULSE_INTERVAL_MSEC = 1000,
38
39    /* unless the tracker says otherwise, rescrape this frequently */
40    DEFAULT_SCRAPE_INTERVAL_SEC = (60 * 15),
41
42    /* unless the tracker says otherwise, this is the announce interval */
43    DEFAULT_ANNOUNCE_INTERVAL_SEC = (60 * 4),
44
45    /* unless the tracker says otherwise, this is the announce min_interval */
46    DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC = (60 * 2),
47
48    /* how long to wait before a rescrape the first time we get an error */
49    FIRST_SCRAPE_RETRY_INTERVAL_SEC = 30,
50
51    /* how long to wait before a reannounce the first time we get an error */
52    FIRST_ANNOUNCE_RETRY_INTERVAL_SEC = 30,
53
54    /* the value of the 'numwant' argument passed in tracker requests. */
55    NUMWANT = 80,
56
57    /* the length of the 'key' argument passed in tracker requests */
58    KEYLEN = 10
59};
60
61/**
62***
63**/
64
65struct tr_tracker
66{
67    unsigned int isRunning     : 1;
68
69    uint8_t randOffset;
70
71    /* sent as the "key" argument in tracker requests
72       to verify us if our IP address changes.
73       This is immutable for the life of the tracker object. */
74    char key_param[KEYLEN+1];
75
76    /* these are set from the latest scrape or tracker response */
77    int announceIntervalSec;
78    int announceMinIntervalSec;
79    int scrapeIntervalSec;
80    int retryScrapeIntervalSec;
81    int retryAnnounceIntervalSec;
82
83    /* index into the torrent's tr_info.trackers array */
84    int trackerIndex;
85
86    tr_session * session;
87
88    tr_publisher_t * publisher;
89
90    /* torrent hash string */
91    uint8_t hash[SHA_DIGEST_LENGTH];
92    char escaped[SHA_DIGEST_LENGTH*3 + 1];
93    char * name;
94
95    /* corresponds to the peer_id sent as a tracker request parameter.
96       one tracker admin says: "When the same torrent is opened and
97       closed and opened again without quitting Transmission ...
98       change the peerid. It would help sometimes if a stopped event
99       was missed to ensure that we didn't think someone was cheating. */
100    uint8_t * peer_id;
101
102    /* these are set from the latest tracker response... -1 is 'unknown' */
103    int timesDownloaded;
104    int seederCount;
105    int leecherCount;
106    char * trackerID;
107
108    time_t manualAnnounceAllowedAt;
109    time_t reannounceAt;
110    time_t scrapeAt;
111
112    time_t lastScrapeTime;
113    long lastScrapeResponse;
114
115    time_t lastAnnounceTime;
116    long lastAnnounceResponse;
117};
118
119#define dbgmsg(name, fmt...) tr_deepLog(__FILE__, __LINE__, name, ##fmt )
120
121/***
122****
123***/
124
125static const tr_tracker_info *
126getCurrentAddressFromTorrent( tr_tracker * t, const tr_torrent * tor )
127{
128    /* user might have removed trackers,
129     * so check to make sure our current index is in-bounds */
130    if( t->trackerIndex >= tor->info.trackerCount )
131        t->trackerIndex = 0;
132
133    assert( t->trackerIndex >= 0 );
134    assert( t->trackerIndex < tor->info.trackerCount );
135    return tor->info.trackers + t->trackerIndex;
136}
137   
138static const tr_tracker_info *
139getCurrentAddress( tr_tracker * t )
140{
141    const tr_torrent * torrent;
142    if(( torrent = tr_torrentFindFromHash( t->session, t->hash )))
143        return getCurrentAddressFromTorrent( t, torrent );
144    return NULL;
145}
146
147static int
148trackerSupportsScrape( tr_tracker * t, const tr_torrent * tor )
149{
150    const tr_tracker_info * info = getCurrentAddressFromTorrent( t, tor );
151    return info && info->scrape;
152}
153
154/***
155****
156***/
157
158static tr_tracker *
159findTracker( tr_session * session, const uint8_t * hash )
160{
161    tr_torrent * torrent = tr_torrentFindFromHash( session, hash );
162    return torrent ? torrent->tracker : NULL;
163}
164
165/***
166****  PUBLISH
167***/
168
169static const tr_tracker_event emptyEvent = { 0, NULL, NULL, NULL, 0, 0 };
170
171static void
172publishMessage( tr_tracker * t, const char * msg, int type )
173{
174    if( t )
175    {
176        tr_tracker_event event = emptyEvent;
177        event.hash = t->hash;
178        event.messageType = type;
179        event.text = msg;
180        tr_publisherPublish( t->publisher, t, &event );
181    }
182}
183
184static void
185publishErrorClear( tr_tracker * t )
186{
187    publishMessage( t, NULL, TR_TRACKER_ERROR_CLEAR );
188}
189
190static void
191publishErrorMessageAndStop( tr_tracker * t, const char * msg )
192{
193    t->isRunning = 0;
194    publishMessage( t, msg, TR_TRACKER_ERROR );
195}
196
197static void
198publishWarning( tr_tracker * t, const char * msg )
199{
200    publishMessage( t, msg, TR_TRACKER_WARNING );
201}
202
203static void
204publishNewPeers( tr_tracker * t, int allAreSeeds,
205                 void * compact, int compactLen )
206{
207    tr_tracker_event event = emptyEvent;
208    event.hash = t->hash;
209    event.messageType = TR_TRACKER_PEERS;
210    event.allAreSeeds = allAreSeeds;
211    event.compact = compact;
212    event.compactLen = compactLen;
213    if( compactLen )
214        tr_publisherPublish( t->publisher, t, &event );
215}
216
217/***
218****
219***/
220
221static void onReqDone( tr_session * session );
222
223static int
224updateAddresses( tr_tracker  * t, int success )
225{
226    int retry;
227
228    tr_torrent * torrent = tr_torrentFindFromHash( t->session, t->hash );
229
230    if( success )
231    {
232        /* multitracker spec: "if a connection with a tracker is
233           successful, it will be moved to the front of the tier." */
234        t->trackerIndex = tr_torrentPromoteTracker( torrent, t->trackerIndex );
235        retry = FALSE; /* we succeeded; no need to retry */
236    }
237    else if ( ++t->trackerIndex >= torrent->info.trackerCount )
238    {
239        t->trackerIndex = 0;
240        retry = FALSE; /* we've tried them all */
241    }
242    else
243    {
244        const tr_tracker_info * n = getCurrentAddressFromTorrent( t, torrent );
245        tr_ninf( t->name, _( "Trying tracker \"%s\"" ), n->announce );
246        retry = TRUE;
247    }
248
249    return retry;
250}
251
252/* Convert to compact form */
253static uint8_t *
254parseOldPeers( tr_benc * bePeers, size_t * byteCount )
255{
256    int i;
257    uint8_t *compact, *walk;
258    const int peerCount = bePeers->val.l.count;
259
260    assert( bePeers->type == TYPE_LIST );
261
262    compact = tr_new( uint8_t, peerCount*6 );
263
264    for( i=0, walk=compact; i<peerCount; ++i )
265    {
266        const char * s;
267        int64_t itmp;
268        struct in_addr addr;
269        tr_port_t port;
270        tr_benc * peer = &bePeers->val.l.vals[i];
271
272        if( !tr_bencDictFindStr( peer, "ip", &s ) || tr_netResolve( s, &addr ) )
273            continue;
274
275        memcpy( walk, &addr, 4 );
276        walk += 4;
277
278        if( !tr_bencDictFindInt( peer, "port", &itmp ) || itmp<0 || itmp>0xffff )
279            continue;
280
281        port = htons( itmp );
282        memcpy( walk, &port, 2 );
283        walk += 2;
284    }
285
286    *byteCount = peerCount * 6;
287    return compact;
288}
289
290static void
291onStoppedResponse( tr_session    * session,
292                   long            responseCode  UNUSED,
293                   const void    * response      UNUSED,
294                   size_t          responseLen   UNUSED,
295                   void          * torrent_hash  )
296{
297    dbgmsg( NULL, "got a response to some `stop' message" );
298    tr_free( torrent_hash );
299    onReqDone( session );
300}
301
302static void
303onTrackerResponse( tr_session    * session,
304                   long            responseCode,
305                   const void    * response,
306                   size_t          responseLen,
307                   void          * torrent_hash )
308{
309    int retry;
310    int success = FALSE;
311    tr_tracker * t;
312
313    onReqDone( session );
314    t = findTracker( session, torrent_hash );
315    tr_free( torrent_hash );
316    if( !t ) /* tracker's been closed */
317        return;
318
319    dbgmsg( t->name, "tracker response: %ld", responseCode );
320    tr_ndbg( t->name, "tracker response: %ld", responseCode );
321    t->lastAnnounceResponse = responseCode;
322
323    if( responseCode == HTTP_OK )
324    {
325        tr_benc benc;
326        const int bencLoaded = !tr_bencLoad( response, responseLen,
327                                             &benc, NULL );
328        publishErrorClear( t );
329        if( bencLoaded && tr_bencIsDict( &benc ) )
330        {
331            tr_benc * tmp;
332            int64_t i;
333            int incomplete = -1;
334            const char * str;
335
336            success = TRUE;
337            t->retryAnnounceIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
338
339            if(( tr_bencDictFindStr( &benc, "failure reason", &str ))) {
340                publishMessage( t, str, TR_TRACKER_ERROR );
341                success = FALSE;
342            }
343
344            if(( tr_bencDictFindStr( &benc, "warning message", &str )))
345                publishWarning( t, str );
346
347            if(( tr_bencDictFindInt( &benc, "interval", &i ))) {
348                dbgmsg( t->name, "setting interval to %d", (int)i );
349                t->announceIntervalSec = i;
350            }
351
352            if(( tr_bencDictFindInt( &benc, "min interval", &i ))) {
353                dbgmsg( t->name, "setting min interval to %d", (int)i );
354                t->announceMinIntervalSec = i;
355            }
356
357            if(( tr_bencDictFindStr( &benc, "tracker id", &str )))
358                t->trackerID = tr_strdup( str );
359
360            if(( tr_bencDictFindInt( &benc, "complete", &i )))
361                t->seederCount = i;
362
363            if(( tr_bencDictFindInt( &benc, "incomplete", &i )))
364                t->leecherCount = incomplete = i;
365
366            if(( tmp = tr_bencDictFind( &benc, "peers" )))
367            {
368                const int allAreSeeds = incomplete == 0;
369
370                if( tmp->type == TYPE_STR ) /* "compact" extension */
371                {
372                    publishNewPeers( t, allAreSeeds, tmp->val.s.s, tmp->val.s.i );
373                }
374                else if( tmp->type == TYPE_LIST ) /* original protocol */
375                {
376                    size_t byteCount = 0;
377                    uint8_t * compact = parseOldPeers( tmp, &byteCount );
378                    publishNewPeers( t, allAreSeeds, compact, byteCount );
379                    tr_free( compact );
380                }
381            }
382        }
383
384        if( bencLoaded )
385            tr_bencFree( &benc );
386    }
387
388    retry = updateAddresses( t, success );
389
390    /**
391    ***
392    **/
393
394    if( retry )
395        responseCode = 300;
396
397    if( 200<=responseCode && responseCode<=299 )
398    {
399        const int interval = t->announceIntervalSec + t->randOffset;
400        const time_t now = time ( NULL );
401        dbgmsg( t->name, "request succeeded. reannouncing in %d seconds", interval );
402        t->scrapeAt = now + t->scrapeIntervalSec + t->randOffset;
403        t->reannounceAt = now + interval;
404        t->manualAnnounceAllowedAt = now + t->announceMinIntervalSec;
405
406        /* #319: save the .resume file after an announce so that, in case
407         * of a crash, our stats still match up with the tracker's stats */
408        tr_torrentSaveResume( tr_torrentFindFromHash( t->session, t->hash ) );
409    }
410    else if( 300<=responseCode && responseCode<=399 )
411    {
412        /* it's a redirect... updateAddresses() has already
413         * parsed the redirect, all that's left is to retry */
414        const int interval = 5;
415        dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
416        t->reannounceAt = time( NULL ) + interval;
417        t->manualAnnounceAllowedAt = time( NULL ) + t->announceMinIntervalSec;
418    }
419    else if( 400<=responseCode && responseCode<=499 )
420    {
421        /* The request could not be understood by the server due to
422         * malformed syntax. The client SHOULD NOT repeat the
423         * request without modifications. */
424        publishErrorMessageAndStop( t, _( "Tracker returned a 4xx message" ) );
425        t->manualAnnounceAllowedAt = ~(time_t)0;
426        t->reannounceAt = 0;
427    }
428    else if( 500<=responseCode && responseCode<=599 )
429    {
430        /* Response status codes beginning with the digit "5" indicate
431         * cases in which the server is aware that it has erred or is
432         * incapable of performing the request.  So we pause a bit and
433         * try again. */
434        t->manualAnnounceAllowedAt = ~(time_t)0;
435        t->reannounceAt = time( NULL ) + t->retryAnnounceIntervalSec;
436        t->retryAnnounceIntervalSec *= 2;
437    }
438    else
439    {
440        /* WTF did we get?? */
441        dbgmsg( t->name, "Invalid response from tracker... retrying in two minutes." );
442        t->manualAnnounceAllowedAt = ~(time_t)0;
443        t->reannounceAt = time( NULL ) + t->randOffset + 120;
444    }
445}
446
447static void
448onScrapeResponse( tr_session   * session,
449                  long           responseCode,
450                  const void   * response,
451                  size_t         responseLen,
452                  void         * torrent_hash )
453{
454    int success = FALSE;
455    int retry;
456    tr_tracker * t;
457
458    onReqDone( session );
459    t = findTracker( session, torrent_hash );
460    tr_free( torrent_hash );
461    if( !t ) /* tracker's been closed... */
462        return;
463
464    dbgmsg( t->name, "scrape response: %ld\n", responseCode );
465    tr_ndbg( t->name, "scrape response: %ld", responseCode );
466    t->lastScrapeResponse = responseCode;
467
468    if( responseCode == HTTP_OK )
469    {
470        tr_benc benc, *files;
471        const int bencLoaded = !tr_bencLoad( response, responseLen,
472                                             &benc, NULL );
473        if( bencLoaded && tr_bencDictFindDict( &benc, "files", &files ) )
474        {
475            int i;
476            for( i=0; i<files->val.l.count; i+=2 )
477            {
478                int64_t itmp;
479                const uint8_t* hash =
480                    (const uint8_t*) files->val.l.vals[i].val.s.s;
481                tr_benc * flags;
482                tr_benc * tordict = &files->val.l.vals[i+1];
483                if( memcmp( t->hash, hash, SHA_DIGEST_LENGTH ) )
484                    continue;
485
486                publishErrorClear( t );
487
488                if(( tr_bencDictFindInt( tordict, "complete", &itmp )))
489                    t->seederCount = itmp;
490
491                if(( tr_bencDictFindInt( tordict, "incomplete", &itmp )))
492                    t->leecherCount = itmp;
493
494                if(( tr_bencDictFindInt( tordict, "downloaded", &itmp )))
495                    t->timesDownloaded = itmp;
496
497                if( tr_bencDictFindDict( tordict, "flags", &flags ))
498                    if(( tr_bencDictFindInt( flags, "min_request_interval", &itmp )))
499                        t->scrapeIntervalSec = i;
500
501                /* as per ticket #1045, safeguard against trackers returning
502                 * a very low min_request_interval... */
503                if( t->scrapeIntervalSec < DEFAULT_SCRAPE_INTERVAL_SEC )
504                    t->scrapeIntervalSec = DEFAULT_SCRAPE_INTERVAL_SEC;
505
506                tr_ndbg( t->name, "Scrape successful.  Rescraping in %d seconds.",
507                         t->scrapeIntervalSec );
508
509                success = TRUE;
510                t->retryScrapeIntervalSec = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
511            }
512        }
513
514        if( bencLoaded )
515            tr_bencFree( &benc );
516    }
517
518    retry = updateAddresses( t, success );
519
520    /**
521    ***
522    **/
523
524    if( retry )
525        responseCode = 300;
526
527    if( 200<=responseCode && responseCode<=299 )
528    {
529        const int interval = t->scrapeIntervalSec + t->randOffset;
530        dbgmsg( t->name, "request succeeded. rescraping in %d seconds", interval );
531        tr_ndbg( t->name, "request succeeded. rescraping in %d seconds", interval );
532        t->scrapeAt = time( NULL ) + interval;
533    }
534    else if( 300<=responseCode && responseCode<=399 )
535    {
536        const int interval = 5;
537        dbgmsg( t->name, "got a redirect. retrying in %d seconds", interval );
538        t->scrapeAt = time( NULL ) + interval;
539    }
540    else
541    {
542        const int interval = t->retryScrapeIntervalSec + t->randOffset;
543        dbgmsg( t->name, "Tracker responded to scrape with %ld.  Retrying in %d seconds.",
544                   responseCode,  interval );
545        t->retryScrapeIntervalSec *= 2;
546        t->scrapeAt = time( NULL ) + interval;
547    }
548}
549
550/***
551****
552***/
553
554enum
555{
556    TR_REQ_STARTED,
557    TR_REQ_COMPLETED,
558    TR_REQ_STOPPED,
559    TR_REQ_REANNOUNCE,
560    TR_REQ_SCRAPE,
561    TR_REQ_COUNT
562};
563
564struct tr_tracker_request
565{
566    uint8_t torrent_hash[SHA_DIGEST_LENGTH];
567    int reqtype; /* TR_REQ_* */
568    char * url;
569    tr_web_done_func * done_func;
570    tr_session * session;
571};
572
573static void
574freeRequest( struct tr_tracker_request * req )
575{
576    tr_free( req->url );
577    tr_free( req );
578}
579
580static void
581buildTrackerRequestURI( tr_tracker        * t,
582                        const tr_torrent  * torrent,
583                        const char        * eventName,
584                        struct evbuffer   * buf )
585{
586    const int isStopping = !strcmp( eventName, "stopped" );
587    const int numwant = isStopping ? 0 : NUMWANT;
588    const char * ann = getCurrentAddressFromTorrent(t,torrent)->announce;
589   
590    evbuffer_add_printf( buf, "%cinfo_hash=%s"
591                              "&peer_id=%s"
592                              "&port=%d"
593                              "&uploaded=%"PRIu64
594                              "&downloaded=%"PRIu64
595                              "&corrupt=%"PRIu64
596                              "&left=%"PRIu64
597                              "&compact=1"
598                              "&numwant=%d"
599                              "&key=%s"
600                              "%s%s"
601                              "%s%s",
602        strchr(ann, '?') ? '&' : '?',
603        t->escaped,
604        t->peer_id,
605        tr_sessionGetPeerPort( t->session ),
606        torrent->uploadedCur,
607        torrent->downloadedCur,
608        torrent->corruptCur,
609        tr_cpLeftUntilComplete( torrent->completion ),
610        numwant,
611        t->key_param,
612        ( ( eventName && *eventName ) ? "&event=" : "" ),
613        ( ( eventName && *eventName ) ? eventName : "" ),
614        ( ( t->trackerID && *t->trackerID ) ? "&trackerid=" : "" ),
615        ( ( t->trackerID && *t->trackerID ) ? t->trackerID : "" ) );
616}
617
618static struct tr_tracker_request*
619createRequest( tr_session * session, tr_tracker * tracker, int reqtype )
620{
621    static const char* strings[] = { "started", "completed", "stopped", "", "err" };
622    const tr_torrent * torrent = tr_torrentFindFromHash( session, tracker->hash );
623    const tr_tracker_info * address = getCurrentAddressFromTorrent( tracker, torrent );
624    const int isStopping = reqtype == TR_REQ_STOPPED;
625    struct tr_tracker_request * req;
626    struct evbuffer * url;
627
628    url = evbuffer_new( );
629    evbuffer_add_printf( url, "%s", address->announce );
630    buildTrackerRequestURI( tracker, torrent, strings[reqtype], url );
631
632    req = tr_new0( struct tr_tracker_request, 1 );
633    req->session = session;
634    req->reqtype = reqtype;
635    req->done_func =  isStopping ? onStoppedResponse : onTrackerResponse;
636    req->url = tr_strdup( EVBUFFER_DATA( url ) );
637    memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
638
639    evbuffer_free( url );
640    return req;
641}
642
643static struct tr_tracker_request*
644createScrape( tr_session * session, tr_tracker * tracker )
645{
646    const tr_tracker_info * a = getCurrentAddress( tracker );
647    struct tr_tracker_request * req;
648    struct evbuffer * url = evbuffer_new( );
649
650    evbuffer_add_printf( url, "%s%cinfo_hash=%s",
651                         a->scrape, strchr(a->scrape,'?')?'&':'?',
652                         tracker->escaped ); 
653
654    req = tr_new0( struct tr_tracker_request, 1 );
655    req->session = session;
656    req->reqtype = TR_REQ_SCRAPE;
657    req->url = tr_strdup( EVBUFFER_DATA( url ) );
658    req->done_func = onScrapeResponse;
659    memcpy( req->torrent_hash, tracker->hash, SHA_DIGEST_LENGTH );
660
661    evbuffer_free( url );
662    return req;
663}
664
665struct tr_tracker_handle
666{
667    int runningCount;
668    tr_timer * pulseTimer;
669};
670
671static int trackerPulse( void * vsession );
672
673static void
674ensureGlobalsExist( tr_session * session )
675{
676    if( session->tracker == NULL )
677    {
678        session->tracker = tr_new0( struct tr_tracker_handle, 1 );
679        session->tracker->pulseTimer = tr_timerNew( session, trackerPulse, session, PULSE_INTERVAL_MSEC );
680        dbgmsg( NULL, "creating tracker timer" );
681    }
682}
683
684void
685tr_trackerSessionClose( tr_session * session )
686{
687    if( session && session->tracker )
688    {
689        dbgmsg( NULL, "freeing tracker timer" );
690        tr_timerFree( &session->tracker->pulseTimer );
691        tr_free( session->tracker );
692        session->tracker = NULL;
693    }
694}
695
696/***
697****
698***/
699
700static void
701invokeRequest( void * vreq )
702{
703    struct tr_tracker_request * req = vreq;
704    tr_tracker * t = findTracker( req->session, req->torrent_hash );
705
706    if( t )
707    {
708        const time_t now = time( NULL );
709
710        if( req->reqtype == TR_REQ_SCRAPE )
711        {
712            t->lastScrapeTime = now;
713            t->scrapeAt = 1;
714        }
715        else
716        {
717            t->lastAnnounceTime = now;
718            t->reannounceAt = 1;
719            t->manualAnnounceAllowedAt = 1;
720            t->scrapeAt = req->reqtype == TR_REQ_STOPPED
721                        ? now + t->scrapeIntervalSec + t->randOffset
722                        : 0;
723        }
724    }
725
726    ++req->session->tracker->runningCount;
727
728    tr_webRun( req->session, req->url, NULL, req->done_func,
729               tr_memdup( req->torrent_hash, SHA_DIGEST_LENGTH ) );
730
731    freeRequest( req );
732}
733
734static void ensureGlobalsExist( tr_session * );
735
736static void
737enqueueScrape( tr_session * session, tr_tracker * tracker )
738{
739    struct tr_tracker_request * req;
740    ensureGlobalsExist( session );
741    req = createScrape( session, tracker );
742    tr_runInEventThread( session, invokeRequest, req );
743}
744
745static void
746enqueueRequest( tr_session * session, tr_tracker * tracker, int reqtype )
747{
748    struct tr_tracker_request * req;
749    ensureGlobalsExist( session );
750    req = createRequest( session, tracker, reqtype );
751    tr_runInEventThread( session, invokeRequest, req );
752}
753
754static int
755trackerPulse( void * vsession )
756{
757    tr_session * session = vsession;
758    struct tr_tracker_handle * th = session->tracker;
759    tr_torrent * tor;
760    const time_t now = time( NULL );
761
762    if( !session->tracker )
763        return FALSE;
764
765    if( th->runningCount )
766        dbgmsg( NULL, "tracker pulse... %d running", th->runningCount );
767
768    /* upkeep: queue periodic rescrape / reannounce */
769    for( tor=session->torrentList; tor; tor=tor->next )
770    {
771        tr_tracker * t = tor->tracker;
772
773        if( ( t->scrapeAt > 1 ) &&
774            ( t->scrapeAt <= now ) &&
775            ( trackerSupportsScrape( t, tor ) ) )
776        {
777            t->scrapeAt = 1;
778            enqueueScrape( session, t );
779        }
780
781        if( ( t->reannounceAt > 1 ) && 
782            ( t->reannounceAt <= now ) &&
783            ( t->isRunning ) )
784        {
785            t->reannounceAt = 1;
786            t->manualAnnounceAllowedAt = 1;
787            enqueueRequest( session, t, TR_REQ_REANNOUNCE );
788        }
789    }
790
791    if( th->runningCount )
792        dbgmsg( NULL, "tracker pulse after upkeep... %d running", th->runningCount );
793
794    /* free the tracker manager if no torrents are left */
795    if(    ( session->tracker )
796        && ( session->tracker->runningCount < 1 )
797        && ( session->torrentList == NULL ) )
798    {
799        tr_trackerSessionClose( session );
800    }
801
802    /* if there are still running torrents (as indicated by
803     * the existence of the tracker manager) then keep the
804     * trackerPulse() timer alive */
805    return session->tracker != NULL;
806}
807
808static void
809onReqDone( tr_session * session )
810{
811    if( session->tracker )
812    {
813        --session->tracker->runningCount;
814        dbgmsg( NULL, "decrementing runningCount to %d", session->tracker->runningCount );
815        trackerPulse( session );
816    }
817}
818
819/***
820****  LIFE CYCLE
821***/
822
823static void
824generateKeyParam( char * msg, int len )
825{
826    int i;
827    const char * pool = "abcdefghijklmnopqrstuvwxyz0123456789";
828    const int poolSize = strlen( pool );
829    for( i=0; i<len; ++i )
830        *msg++ = pool[tr_cryptoRandInt(poolSize)];
831    *msg = '\0';
832}
833
834static int
835is_rfc2396_alnum( char ch )
836{
837    return     ( '0' <= ch && ch <= '9' )
838            || ( 'A' <= ch && ch <= 'Z' )
839            || ( 'a' <= ch && ch <= 'z' );
840}
841
842static void
843escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
844{
845    const uint8_t *end = in + in_len;
846    while( in != end )
847        if( is_rfc2396_alnum(*in) )
848            *out++ = (char) *in++;
849        else 
850            out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
851    *out = '\0';
852}
853
854tr_tracker *
855tr_trackerNew( const tr_torrent * torrent )
856{
857    const tr_info * info = &torrent->info;
858    tr_tracker * t;
859
860    t = tr_new0( tr_tracker, 1 );
861    t->publisher = tr_publisherNew( );
862    t->session                  = torrent->handle;
863    t->scrapeIntervalSec        = DEFAULT_SCRAPE_INTERVAL_SEC;
864    t->retryScrapeIntervalSec   = FIRST_SCRAPE_RETRY_INTERVAL_SEC;
865    t->retryAnnounceIntervalSec = FIRST_ANNOUNCE_RETRY_INTERVAL_SEC;
866    t->announceIntervalSec      = DEFAULT_ANNOUNCE_INTERVAL_SEC;
867    t->announceMinIntervalSec   = DEFAULT_ANNOUNCE_MIN_INTERVAL_SEC;
868    t->timesDownloaded          = -1;
869    t->seederCount              = -1;
870    t->leecherCount             = -1;
871    t->lastAnnounceResponse     = -1;
872    t->lastScrapeResponse       = -1;
873    t->manualAnnounceAllowedAt  = ~(time_t)0;
874    t->name = tr_strdup( info->name );
875    t->randOffset = tr_cryptoRandInt( 30 );
876    memcpy( t->hash, info->hash, SHA_DIGEST_LENGTH );
877    escape( t->escaped, info->hash, SHA_DIGEST_LENGTH );
878    generateKeyParam( t->key_param, KEYLEN );
879
880    t->trackerIndex = 0;
881
882    if( trackerSupportsScrape( t, torrent ) )
883        t->scrapeAt = time( NULL ) + t->randOffset;
884
885    return t;
886}
887
888static void
889onTrackerFreeNow( void * vt )
890{
891    tr_tracker * t = vt;
892
893    tr_publisherFree( &t->publisher );
894    tr_free( t->name );
895    tr_free( t->trackerID );
896    tr_free( t->peer_id );
897
898    tr_free( t );
899}
900
901/***
902****  PUBLIC
903***/
904
905void
906tr_trackerFree( tr_tracker * t )
907{
908    if( t )
909        tr_runInEventThread( t->session, onTrackerFreeNow, t );
910}
911
912tr_publisher_tag
913tr_trackerSubscribe( tr_tracker          * t,
914                     tr_delivery_func      func,
915                     void                * user_data )
916{
917    return tr_publisherSubscribe( t->publisher, func, user_data );
918}
919
920void
921tr_trackerUnsubscribe( tr_tracker        * t,
922                       tr_publisher_tag    tag )
923{
924    if( t )
925        tr_publisherUnsubscribe( t->publisher, tag );
926}
927
928const tr_tracker_info *
929tr_trackerGetAddress( tr_tracker   * t )
930{
931    return getCurrentAddress( t );
932}
933
934time_t
935tr_trackerGetManualAnnounceTime( const struct tr_tracker * t )
936{
937    return t->isRunning ? t->manualAnnounceAllowedAt : 0;
938}
939
940int
941tr_trackerCanManualAnnounce ( const tr_tracker * t)
942{
943    const time_t allow = tr_trackerGetManualAnnounceTime( t );
944    return allow && ( allow <= time( NULL ) );
945}
946
947void
948tr_trackerGetCounts( const tr_tracker  * t,
949                     int               * setme_completedCount,
950                     int               * setme_leecherCount,
951                     int               * setme_seederCount )
952{
953    if( setme_completedCount )
954       *setme_completedCount = t->timesDownloaded;
955
956    if( setme_leecherCount )
957       *setme_leecherCount = t->leecherCount;
958
959    if( setme_seederCount )
960       *setme_seederCount = t->seederCount;
961}
962
963
964void
965tr_trackerStart( tr_tracker * t )
966{
967    if( t && !t->isRunning )
968    {
969        tr_free( t->peer_id );
970        t->peer_id = tr_peerIdNew( );
971
972        t->isRunning = 1;
973        enqueueRequest( t->session, t, TR_REQ_STARTED );
974    }
975}
976
977void
978tr_trackerReannounce( tr_tracker * t )
979{
980    enqueueRequest( t->session, t, TR_REQ_REANNOUNCE );
981}
982
983void
984tr_trackerCompleted( tr_tracker * t )
985{
986    enqueueRequest( t->session, t, TR_REQ_COMPLETED );
987}
988
989void
990tr_trackerStop( tr_tracker * t )
991{
992    if( t && t->isRunning ) {
993        t->isRunning = 0;
994        t->reannounceAt = t->manualAnnounceAllowedAt = 0;
995        enqueueRequest( t->session, t, TR_REQ_STOPPED );
996    }
997}
998
999void
1000tr_trackerChangeMyPort( tr_tracker * t )
1001{
1002    if( t->isRunning )
1003        tr_trackerReannounce( t );
1004}
1005
1006void
1007tr_trackerStat( const tr_tracker * t,
1008                struct tr_stat   * setme)
1009{
1010    assert( t );
1011    assert( setme );
1012
1013    setme->lastScrapeTime = t->lastScrapeTime;
1014    setme->nextScrapeTime = t->scrapeAt;
1015    setme->lastAnnounceTime = t->lastAnnounceTime;
1016    setme->nextAnnounceTime = t->reannounceAt;
1017    setme->manualAnnounceTime = t->manualAnnounceAllowedAt;
1018
1019    if( t->lastScrapeResponse == -1 ) /* never been scraped */
1020        *setme->scrapeResponse = '\0';
1021    else
1022        tr_snprintf( setme->scrapeResponse,
1023                     sizeof( setme->scrapeResponse ),
1024                     "%s (%ld)",
1025                     tr_webGetResponseStr( t->lastScrapeResponse ),
1026                     t->lastScrapeResponse );
1027
1028    if( t->lastAnnounceResponse == -1 ) /* never been announced */
1029        *setme->announceResponse = '\0';
1030    else
1031        tr_snprintf( setme->announceResponse,
1032                     sizeof( setme->announceResponse ),
1033                     "%s (%ld)",
1034                     tr_webGetResponseStr( t->lastAnnounceResponse ),
1035                     t->lastAnnounceResponse );
1036}
Note: See TracBrowser for help on using the repository browser.