source: trunk/libtransmission/tracker.c @ 5913

Last change on this file since 5913 was 5913, checked in by charles, 14 years ago

sine we now have two public ports (peer and rpc), rename "publicPort" as "peerPort"

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