source: trunk/libtransmission/peer-io.c @ 6782

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

first draft at having more accurate speed controls

  • Property svn:keywords set to Date Rev Author Id
File size: 19.4 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: peer-io.c 6782 2008-09-17 19:44:24Z charles $
11 */
12
13#include <assert.h>
14#include <limits.h> /* INT_MAX */
15#include <string.h>
16#include <stdio.h>
17#include <unistd.h>
18
19#ifdef WIN32
20#include <winsock2.h>
21#else
22#include <netinet/in.h> /* struct in_addr */
23#include <arpa/inet.h> /* inet_ntoa */
24#endif
25
26#include <event.h>
27
28#include "transmission.h"
29#include "crypto.h"
30#include "net.h"
31#include "peer-io.h"
32#include "ratecontrol.h"
33#include "trevent.h"
34#include "utils.h"
35
36#define IO_TIMEOUT_SECS 8
37
38/**
39***
40**/
41
42#define dbgmsg(io, fmt...) \
43    tr_deepLog( __FILE__,__LINE__, tr_peerIoGetAddrStr(io), ##fmt )
44
45struct tr_bandwidth
46{
47    unsigned int          isUnlimited : 1;
48    size_t                bytesUsed;
49    size_t                bytesLeft;
50};
51
52struct tr_peerIo
53{
54    unsigned int          isEncrypted : 1;
55    unsigned int          isIncoming : 1;
56    unsigned int          peerIdIsSet : 1;
57    unsigned int          extendedProtocolSupported : 1;
58    unsigned int          fastPeersSupported : 1;
59
60    uint8_t               encryptionMode;
61    uint8_t               timeout;
62    uint16_t              port;
63    int                   socket;
64
65    uint8_t               peerId[20];
66    time_t                timeCreated;
67
68    tr_session          * session;
69
70    struct in_addr        in_addr;
71    struct bufferevent  * bufev;
72    struct evbuffer     * output;
73
74    tr_can_read_cb        canRead;
75    tr_did_write_cb       didWrite;
76    tr_net_error_cb       gotError;
77    void                * userData;
78
79    size_t                bufferSize[2];
80
81    struct tr_bandwidth   bandwidth[2];
82    tr_ratecontrol      * speedometer[2];
83
84    tr_crypto           * crypto;
85};
86
87/**
88***
89**/
90
91static void
92adjustOutputBuffer( tr_peerIo * io )
93{
94    struct evbuffer * live = EVBUFFER_OUTPUT( io->bufev );
95
96    if( io->bandwidth[TR_UP].isUnlimited )
97    {
98        bufferevent_write_buffer( io->bufev, io->output );
99    }
100    else if( io->bandwidth[TR_UP].bytesLeft > EVBUFFER_LENGTH( live ) )
101    {
102        /* there's free space in bufev's output buffer;
103           try to fill it up */
104        const size_t desiredLength = io->bandwidth[TR_UP].bytesLeft;
105        const size_t under = desiredLength - EVBUFFER_LENGTH( live );
106        const size_t n = MIN( under, EVBUFFER_LENGTH( io->output ) );
107        bufferevent_write( io->bufev, EVBUFFER_DATA( io->output ), n );
108        evbuffer_drain( io->output, n );
109    }
110    else if( io->bandwidth[TR_UP].bytesLeft < EVBUFFER_LENGTH( live ) )
111    {
112        /* bufev's output buffer exceeds our bandwidth allocation;
113           move the excess out of bufev so it can't be sent yet */
114        const size_t desiredLength = io->bandwidth[TR_UP].bytesLeft;
115        const size_t over = EVBUFFER_LENGTH( live ) - desiredLength;
116        struct evbuffer * buf = evbuffer_new( );
117        evbuffer_add( buf, EVBUFFER_DATA( live ) + desiredLength, over );
118        evbuffer_add_buffer( buf, io->output );
119        evbuffer_free( io->output );
120        io->output = buf;
121        EVBUFFER_LENGTH( live ) = desiredLength;
122    }
123    else if( EVBUFFER_LENGTH( live ) )
124    {
125        bufferevent_enable( io->bufev, EV_WRITE );
126    }
127
128    io->bufferSize[TR_UP] = EVBUFFER_LENGTH( live );
129
130    dbgmsg( io, "after adjusting the output buffer, its size is now %zu",
131            io->bufferSize[TR_UP] );
132}
133
134static void
135adjustInputBuffer( tr_peerIo * io )
136{
137    if( io->bandwidth[TR_DOWN].isUnlimited )
138    {
139        dbgmsg( io, "unlimited reading..." );
140        bufferevent_setwatermark( io->bufev, EV_READ, 0, 0 );
141        bufferevent_enable( io->bufev, EV_READ );
142    }
143    else
144    {
145        const size_t n = io->bandwidth[TR_DOWN].bytesLeft;
146        if( n == 0 )
147        {
148            dbgmsg( io, "disabling reads because we've hit our limit" );
149            bufferevent_disable( io->bufev, EV_READ );
150        }
151        else
152        {
153            dbgmsg( io, "enabling reading of %zu more bytes", n );
154            bufferevent_setwatermark( io->bufev, EV_READ, 0, n );
155            bufferevent_enable( io->bufev, EV_READ );
156        }
157    }
158}
159
160/***
161****
162***/
163
164static void
165didWriteWrapper( struct bufferevent * e, void * vio )
166{
167    tr_peerIo * io = vio;
168    const size_t len = EVBUFFER_LENGTH( EVBUFFER_OUTPUT( e ) );
169
170    dbgmsg( io, "didWrite... io->outputBufferSize was %zu, is now %zu",
171            io->bufferSize[TR_UP], len );
172
173    if( len < io->bufferSize[TR_UP] )
174    {
175        const size_t n = io->bufferSize[TR_UP] - len;
176        struct tr_bandwidth * b = &io->bandwidth[TR_UP];
177        b->bytesLeft -= MIN( b->bytesLeft, (size_t)n );
178        b->bytesUsed += n;
179        tr_rcTransferred( io->speedometer[TR_UP], n );
180        dbgmsg( io, "wrote %zu bytes to peer... upload bytesLeft is now %zu",
181                n, b->bytesLeft );
182    }
183
184    adjustOutputBuffer( io );
185
186    if( io->didWrite )
187        io->didWrite( e, io->userData );
188}
189
190static void
191canReadWrapper( struct bufferevent * e, void * vio )
192{
193    int done = 0;
194    tr_peerIo * io = vio;
195    tr_session * session = io->session;
196    const size_t len = EVBUFFER_LENGTH( EVBUFFER_INPUT( e ) );
197
198    dbgmsg( io, "canRead" );
199
200    /* if the input buffer has grown, record the bytes that were read */
201    if( len > io->bufferSize[TR_DOWN] )
202    {
203        const size_t n = len - io->bufferSize[TR_DOWN];
204        struct tr_bandwidth * b = io->bandwidth + TR_DOWN;
205        b->bytesLeft -= MIN( b->bytesLeft, (size_t)n );
206        b->bytesUsed += n;
207        tr_rcTransferred( io->speedometer[TR_DOWN], n );
208        dbgmsg( io, "%zu new input bytes. bytesUsed is %zu, bytesLeft is %zu",
209                n, b->bytesUsed, b->bytesLeft );
210
211        adjustInputBuffer( io );
212    }
213
214    /* try to consume the input buffer */
215    if( io->canRead )
216    {
217        tr_globalLock( session );
218
219        while( !done )
220        {
221            const int ret = io->canRead( e, io->userData );
222
223            switch( ret )
224            {
225                case READ_AGAIN:
226                    if( EVBUFFER_LENGTH( e->input ) )
227                        continue;
228                case READ_MORE:
229                case READ_DONE:
230                    done = 1;
231            }
232        }
233
234        tr_globalUnlock( session );
235    }
236
237    io->bufferSize[TR_DOWN] = EVBUFFER_LENGTH( EVBUFFER_INPUT( e ) );
238}
239
240static void
241gotErrorWrapper( struct bufferevent * e, short what, void * userData )
242{
243    tr_peerIo * c = userData;
244    if( c->gotError )
245        c->gotError( e, what, c->userData );
246}
247
248/**
249***
250**/
251
252static void
253bufevNew( tr_peerIo * io )
254{
255    io->bufev = bufferevent_new( io->socket,
256                                 canReadWrapper,
257                                 didWriteWrapper,
258                                 gotErrorWrapper,
259                                 io );
260
261    /* tell libevent to call didWriteWrapper after every write,
262     * not just when the write buffer is empty */
263    bufferevent_setwatermark( io->bufev, EV_WRITE, INT_MAX, 0 );
264
265    bufferevent_settimeout( io->bufev, io->timeout, io->timeout );
266
267    bufferevent_enable( io->bufev, EV_READ|EV_WRITE );
268}
269
270static tr_peerIo*
271tr_peerIoNew( tr_session             * session,
272              const struct in_addr   * in_addr,
273              uint16_t                 port,
274              const uint8_t          * torrentHash,
275              int                      isIncoming,
276              int                      socket )
277{
278    tr_peerIo * io;
279
280    if( socket >= 0 )
281        tr_netSetTOS( socket, session->peerSocketTOS );
282
283    io = tr_new0( tr_peerIo, 1 );
284    io->crypto = tr_cryptoNew( torrentHash, isIncoming );
285    io->session = session;
286    io->in_addr = *in_addr;
287    io->port = port;
288    io->socket = socket;
289    io->isIncoming = isIncoming != 0;
290    io->timeout = IO_TIMEOUT_SECS;
291    io->timeCreated = time( NULL );
292    io->output = evbuffer_new( );
293    io->bandwidth[TR_UP].isUnlimited = 1;
294    io->bandwidth[TR_DOWN].isUnlimited = 1;
295    io->speedometer[TR_UP] = tr_rcInit( );
296    io->speedometer[TR_DOWN] = tr_rcInit( );
297    bufevNew( io );
298    return io;
299}
300
301tr_peerIo*
302tr_peerIoNewIncoming( tr_session            * session,
303                      const struct in_addr  * in_addr,
304                      uint16_t                port,
305                      int                     socket )
306{
307    assert( session );
308    assert( in_addr );
309    assert( socket >= 0 );
310
311    return tr_peerIoNew( session, in_addr, port,
312                         NULL, 1,
313                         socket );
314}
315
316tr_peerIo*
317tr_peerIoNewOutgoing( tr_session            * session,
318                      const struct in_addr  * in_addr,
319                      int                     port,
320                      const uint8_t         * torrentHash )
321{
322    int socket;
323
324    assert( session );
325    assert( in_addr );
326    assert( port >= 0 );
327    assert( torrentHash );
328
329    socket = tr_netOpenTCP( in_addr, port, 0 );
330
331    return socket < 0
332        ? NULL 
333        : tr_peerIoNew( session, in_addr, port, torrentHash, 0, socket );
334}
335
336static void
337io_dtor( void * vio )
338{
339    tr_peerIo * io = vio;
340
341    tr_rcClose( io->speedometer[TR_DOWN] );
342    tr_rcClose( io->speedometer[TR_UP] );
343    evbuffer_free( io->output );
344    bufferevent_free( io->bufev );
345    tr_netClose( io->socket );
346    tr_cryptoFree( io->crypto );
347    tr_free( io );
348}
349
350void
351tr_peerIoFree( tr_peerIo * io )
352{
353    if( io )
354    {
355        io->canRead = NULL;
356        io->didWrite = NULL;
357        io->gotError = NULL;
358        tr_runInEventThread( io->session, io_dtor, io );
359    }
360}
361
362tr_session*
363tr_peerIoGetSession( tr_peerIo * io )
364{
365    assert( io );
366    assert( io->session );
367
368    return io->session;
369}
370
371const struct in_addr*
372tr_peerIoGetAddress( const tr_peerIo * io, uint16_t * port )
373{
374    assert( io );
375
376    if( port )
377       *port = io->port;
378
379    return &io->in_addr;
380}
381
382const char*
383tr_peerIoAddrStr( const struct in_addr * addr, uint16_t port )
384{
385    static char buf[512];
386    tr_snprintf( buf, sizeof(buf), "%s:%u", inet_ntoa( *addr ), ntohs( port ) );
387    return buf;
388}
389
390const char*
391tr_peerIoGetAddrStr( const tr_peerIo * io )
392{
393    return tr_peerIoAddrStr( &io->in_addr, io->port );
394}
395
396static void
397tr_peerIoTryRead( tr_peerIo * io )
398{
399    if( EVBUFFER_LENGTH( io->bufev->input ) )
400        canReadWrapper( io->bufev, io );
401}
402
403void 
404tr_peerIoSetIOFuncs( tr_peerIo          * io,
405                     tr_can_read_cb       readcb,
406                     tr_did_write_cb      writecb,
407                     tr_net_error_cb      errcb,
408                     void               * userData )
409{
410    io->canRead = readcb;
411    io->didWrite = writecb;
412    io->gotError = errcb;
413    io->userData = userData;
414
415    tr_peerIoTryRead( io );
416}
417
418int
419tr_peerIoIsIncoming( const tr_peerIo * c )
420{
421    return c->isIncoming ? 1 : 0;
422}
423
424int
425tr_peerIoReconnect( tr_peerIo * io )
426{
427    assert( !tr_peerIoIsIncoming( io ) );
428
429    if( io->socket >= 0 )
430        tr_netClose( io->socket );
431
432    io->socket = tr_netOpenTCP( &io->in_addr, io->port, 0 );
433 
434    if( io->socket >= 0 )
435    {
436        tr_netSetTOS( io->socket, io->session->peerSocketTOS );
437
438        bufferevent_free( io->bufev );
439        bufevNew( io );
440        return 0;
441    }
442 
443    return -1;
444}
445
446void
447tr_peerIoSetTimeoutSecs( tr_peerIo * io, int secs )
448{
449    io->timeout = secs;
450    bufferevent_settimeout( io->bufev, io->timeout, io->timeout );
451    bufferevent_enable( io->bufev, EV_READ|EV_WRITE );
452}
453
454/**
455***
456**/
457
458void
459tr_peerIoSetTorrentHash( tr_peerIo     * io,
460                         const uint8_t * hash )
461{
462    assert( io );
463
464    tr_cryptoSetTorrentHash( io->crypto, hash );
465}
466
467const uint8_t*
468tr_peerIoGetTorrentHash( tr_peerIo * io )
469{
470    assert( io );
471    assert( io->crypto );
472
473    return tr_cryptoGetTorrentHash( io->crypto );
474}
475
476int
477tr_peerIoHasTorrentHash( const tr_peerIo * io )
478{
479    assert( io );
480    assert( io->crypto );
481
482    return tr_cryptoHasTorrentHash( io->crypto );
483}
484
485/**
486***
487**/
488
489void
490tr_peerIoSetPeersId( tr_peerIo     * io,
491                     const uint8_t * peer_id )
492{
493    assert( io );
494
495    if(( io->peerIdIsSet = peer_id != NULL ))
496        memcpy( io->peerId, peer_id, 20 );
497    else
498        memset( io->peerId, 0, 20 );
499}
500
501const uint8_t* 
502tr_peerIoGetPeersId( const tr_peerIo * io )
503{
504    assert( io );
505    assert( io->peerIdIsSet );
506
507    return io->peerId;
508}
509
510/**
511***
512**/
513
514void
515tr_peerIoEnableLTEP( tr_peerIo * io, int flag )
516{
517    assert( io );
518    assert( flag==0 || flag==1 );
519   
520    io->extendedProtocolSupported = flag;
521}
522
523void
524tr_peerIoEnableFEXT( tr_peerIo * io, int flag )
525{
526    assert( io );
527    assert( flag==0 || flag==1 );
528   
529    io->fastPeersSupported = flag;
530}
531
532int
533tr_peerIoSupportsLTEP( const tr_peerIo * io )
534{
535    assert( io );
536   
537    return io->extendedProtocolSupported;
538}
539
540int
541tr_peerIoSupportsFEXT( const tr_peerIo * io )
542{
543    assert( io );
544   
545    return io->fastPeersSupported;
546}
547
548/**
549***
550**/
551
552size_t
553tr_peerIoGetBandwidthUsed( const tr_peerIo  * io,
554                           tr_direction       direction )
555{
556    assert( io );
557    assert( direction==TR_UP || direction==TR_DOWN );
558    return io->bandwidth[direction].bytesUsed;
559}
560
561size_t
562tr_peerIoGetBandwidthLeft( const tr_peerIo  * io,
563                           tr_direction       direction )
564{
565    assert( io );
566    assert( direction==TR_UP || direction==TR_DOWN );
567    return io->bandwidth[direction].bytesLeft;
568}
569
570size_t
571tr_peerIoGetWriteBufferSpace( const tr_peerIo * io )
572{
573    const size_t desiredBufferLen = 4096;
574    const size_t currentLiveLen = EVBUFFER_LENGTH( EVBUFFER_OUTPUT( io->bufev ) );
575
576    const size_t desiredLiveLen = tr_peerIoGetBandwidthLeft( io, TR_UP );
577    const size_t currentLbufLen = EVBUFFER_LENGTH( io->output );
578
579    const size_t desiredLen = desiredBufferLen + desiredLiveLen;
580    const size_t currentLen = currentLiveLen + currentLbufLen;
581
582    size_t freeSpace = 0;
583
584    if( desiredLen > currentLen )
585        freeSpace = desiredLen - currentLen;
586    else
587        freeSpace = 0;
588
589    return freeSpace;
590}
591
592void
593tr_peerIoSetBandwidth( tr_peerIo     * io,
594                       tr_direction    direction,
595                       size_t          bytesLeft )
596{
597    struct tr_bandwidth * b;
598
599    assert( io );
600    assert( direction==TR_UP || direction==TR_DOWN );
601
602    b = io->bandwidth + direction;   
603    b->isUnlimited = 0;
604    b->bytesUsed = 0;
605    b->bytesLeft = bytesLeft;
606
607    adjustOutputBuffer( io );
608    adjustInputBuffer( io );
609}
610
611void
612tr_peerIoSetBandwidthUnlimited( tr_peerIo     * io,
613                                tr_direction    direction )
614{
615    struct tr_bandwidth * b;
616
617    assert( io );
618    assert( direction==TR_UP || direction==TR_DOWN );
619
620    b = io->bandwidth + direction;
621    b->isUnlimited = 1;
622    b->bytesUsed = 0;
623    b->bytesLeft = 0;
624
625    adjustInputBuffer( io );
626    adjustOutputBuffer( io );
627}
628
629double
630tr_peerIoGetRateToClient( const tr_peerIo * io )
631{
632    return tr_rcRate( io->speedometer[TR_DOWN] );
633}
634
635double
636tr_peerIoGetRateToPeer( const tr_peerIo * io )
637{
638    return tr_rcRate( io->speedometer[TR_UP] );
639}
640
641
642/**
643***
644**/
645
646tr_crypto* 
647tr_peerIoGetCrypto( tr_peerIo * c )
648{
649    return c->crypto;
650}
651
652void 
653tr_peerIoSetEncryption( tr_peerIo * io,
654                        int         encryptionMode )
655{
656    assert( io );
657    assert( encryptionMode==PEER_ENCRYPTION_NONE
658         || encryptionMode==PEER_ENCRYPTION_RC4 );
659
660    io->encryptionMode = encryptionMode;
661}
662
663int
664tr_peerIoIsEncrypted( const tr_peerIo * io )
665{
666    return io!=NULL && io->encryptionMode==PEER_ENCRYPTION_RC4;
667}
668
669/**
670***
671**/
672
673int
674tr_peerIoWantsBandwidth( const tr_peerIo * io, tr_direction direction )
675{
676    assert( direction==TR_UP || direction==TR_DOWN );
677
678    if( direction == TR_DOWN )
679    {
680        return TRUE; /* FIXME -- is there a good way to test for this? */
681    }
682    else
683    {
684        return EVBUFFER_LENGTH( EVBUFFER_OUTPUT( io->bufev ) )
685            || EVBUFFER_LENGTH( io->output );
686    }
687}
688
689void
690tr_peerIoWrite( tr_peerIo   * io,
691                const void  * writeme,
692                size_t        writemeLen )
693{
694    assert( tr_amInEventThread( io->session ) );
695    dbgmsg( io, "adding %zu bytes into io->output", writemeLen );
696
697    if( io->bandwidth[TR_UP].isUnlimited )
698        bufferevent_write( io->bufev, writeme, writemeLen );
699    else
700        evbuffer_add( io->output, writeme, writemeLen );
701
702    adjustOutputBuffer( io );
703}
704
705void
706tr_peerIoWriteBuf( tr_peerIo       * io,
707                   struct evbuffer * buf )
708{
709    const size_t n = EVBUFFER_LENGTH( buf );
710    tr_peerIoWrite( io, EVBUFFER_DATA( buf ), n );
711    evbuffer_drain( buf, n );
712}
713
714/**
715***
716**/
717
718void
719tr_peerIoWriteBytes( tr_peerIo        * io,
720                     struct evbuffer  * outbuf,
721                     const void       * bytes,
722                     size_t             byteCount )
723{
724    uint8_t * tmp;
725
726    switch( io->encryptionMode )
727    {
728        case PEER_ENCRYPTION_NONE:
729            evbuffer_add( outbuf, bytes, byteCount );
730            break;
731
732        case PEER_ENCRYPTION_RC4:
733            tmp = tr_new( uint8_t, byteCount );
734            tr_cryptoEncrypt( io->crypto, byteCount, bytes, tmp );
735            evbuffer_add( outbuf, tmp, byteCount );
736            tr_free( tmp );
737            break;
738
739        default:
740            assert( 0 );
741    }
742}
743
744void
745tr_peerIoWriteUint8( tr_peerIo        * io,
746                     struct evbuffer  * outbuf,
747                     uint8_t            writeme )
748{
749    tr_peerIoWriteBytes( io, outbuf, &writeme, sizeof(uint8_t) );
750}
751
752void
753tr_peerIoWriteUint16( tr_peerIo        * io,
754                      struct evbuffer  * outbuf,
755                      uint16_t           writeme )
756{
757    uint16_t tmp = htons( writeme );
758    tr_peerIoWriteBytes( io, outbuf, &tmp, sizeof(uint16_t) );
759}
760
761void
762tr_peerIoWriteUint32( tr_peerIo        * io,
763                      struct evbuffer  * outbuf,
764                      uint32_t           writeme )
765{
766    uint32_t tmp = htonl( writeme );
767    tr_peerIoWriteBytes( io, outbuf, &tmp, sizeof(uint32_t) );
768}
769
770/***
771****
772***/
773
774void
775tr_peerIoReadBytes( tr_peerIo        * io,
776                    struct evbuffer  * inbuf,
777                    void             * bytes,
778                    size_t             byteCount )
779{
780    assert( EVBUFFER_LENGTH( inbuf ) >= byteCount );
781
782    switch( io->encryptionMode )
783    {
784        case PEER_ENCRYPTION_NONE:
785            evbuffer_remove( inbuf, bytes, byteCount );
786            break;
787
788        case PEER_ENCRYPTION_RC4:
789            evbuffer_remove( inbuf, bytes, byteCount );
790            tr_cryptoDecrypt( io->crypto, byteCount, bytes, bytes );
791            break;
792
793        default:
794            assert( 0 );
795    }
796}
797
798void
799tr_peerIoReadUint8( tr_peerIo         * io,
800                    struct evbuffer   * inbuf,
801                    uint8_t           * setme )
802{
803    tr_peerIoReadBytes( io, inbuf, setme, sizeof(uint8_t) );
804}
805
806void
807tr_peerIoReadUint16( tr_peerIo         * io,
808                     struct evbuffer   * inbuf,
809                     uint16_t          * setme )
810{
811    uint16_t tmp;
812    tr_peerIoReadBytes( io, inbuf, &tmp, sizeof(uint16_t) );
813    *setme = ntohs( tmp );
814}
815
816void
817tr_peerIoReadUint32( tr_peerIo         * io,
818                     struct evbuffer   * inbuf,
819                     uint32_t          * setme )
820{
821    uint32_t tmp;
822    tr_peerIoReadBytes( io, inbuf, &tmp, sizeof(uint32_t) );
823    *setme = ntohl( tmp );
824}
825
826void
827tr_peerIoDrain( tr_peerIo        * io,
828                struct evbuffer  * inbuf,
829                size_t             byteCount )
830{
831    uint8_t * tmp = tr_new( uint8_t, byteCount );
832    tr_peerIoReadBytes( io, inbuf, tmp, byteCount );
833    tr_free( tmp );
834}
835
836int
837tr_peerIoGetAge( const tr_peerIo * io )
838{
839    return time( NULL ) - io->timeCreated;
840}
Note: See TracBrowser for help on using the repository browser.