source: trunk/libtransmission/peerparse.h @ 2177

Last change on this file since 2177 was 2177, checked in by charles, 15 years ago

adding experimental implementation of Tamilmani's `Swift' tit-for-tat algorithm for testing. To tweak or disable, change the values around line 50 of libtransmission/peer.c

  • Property svn:keywords set to Date Rev Author Id
File size: 21.0 KB
Line 
1/******************************************************************************
2 * $Id: peerparse.h 2177 2007-06-21 14:47:26Z charles $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25/***********************************************************************
26 * This file handles all incoming messages
27 **********************************************************************/
28
29/***********************************************************************
30 * parseChoke
31 ***********************************************************************
32 *
33 **********************************************************************/
34static inline int parseChoke( tr_torrent_t * tor, tr_peer_t * peer,
35                              int len, int choking )
36{
37    tr_request_t * r;
38    int i;
39
40    if( len != 0 )
41    {
42        peer_dbg( "GET  %schoke, invalid", choking ? "" : "un" );
43        return TR_ERROR_ASSERT;
44    }
45
46    peer_dbg( "GET  %schoke", choking ? "" : "un" );
47
48    peer->peerChoking = choking;
49
50    if( choking )
51    {
52        /* Discard all pending requests */
53        for( i = 0; i < peer->inRequestCount; i++ )
54        {
55            r = &peer->inRequests[i];
56            tr_cpDownloaderRem( tor->completion, tr_block(r->index,r->begin) );
57
58            /* According to the spec, all requests are dropped when you
59               are choked, however some clients seem to remember those
60               the next time they unchoke you. Also, if you get quickly
61               choked and unchoked while you are sending requests, you
62               can't know when the other peer received them and how it
63               handled it.
64               This can cause us to receive blocks multiple times and
65               overdownload, so we send 'cancel' messages to try and
66               reduce that. */
67            sendCancel( peer, r->index, r->begin, r->length );
68        }
69        peer->inRequestCount = 0;
70    }
71
72    return TR_OK;
73}
74
75/***********************************************************************
76 * parseInterested
77 ***********************************************************************
78 *
79 **********************************************************************/
80static inline int parseInterested( tr_peer_t * peer, int len,
81                                   int interested )
82{
83    if( len != 0 )
84    {
85        peer_dbg( "GET  %sinterested, invalid", interested ? "" : "un" );
86        return TR_ERROR_ASSERT;
87    }
88
89    peer_dbg( "GET  %sinterested", interested ? "" : "un" );
90
91    peer->peerInterested = interested;
92
93    return TR_OK;
94}
95
96/***********************************************************************
97 * parseHave
98 ***********************************************************************
99 *
100 **********************************************************************/
101static inline int parseHave( tr_torrent_t * tor, tr_peer_t * peer,
102                             uint8_t * p, int len )
103{
104    tr_info_t * inf = &tor->info;
105    uint32_t piece;
106
107    if( len != 4 )
108    {
109        peer_dbg( "GET  have, invalid" );
110        return TR_ERROR_ASSERT;
111    }
112
113    TR_NTOHL( p, piece );
114    if( ( uint32_t )inf->pieceCount <= piece )
115    {
116        peer_dbg( "GET  have, invalid piece" );
117        return TR_ERROR_ASSERT;
118    }
119
120    peer_dbg( "GET  have %d", piece );
121
122    if( !peer->bitfield )
123    {
124        peer->bitfield = tr_bitfieldNew( inf->pieceCount );
125    }
126    if( !tr_bitfieldHas( peer->bitfield, piece ) )
127    {
128        peer->pieceCount++;
129        peer->progress = (float) peer->pieceCount / inf->pieceCount;
130    }
131    tr_bitfieldAdd( peer->bitfield, piece );
132    updateInterest( tor, peer );
133
134    tr_rcTransferred( tor->swarmspeed, tor->info.pieceSize );
135
136    return TR_OK;
137}
138
139static inline int parseBitfield( tr_torrent_t * tor, tr_peer_t * peer,
140                                 uint8_t * p, int len )
141{
142    tr_info_t * inf = &tor->info;
143    int bitfieldSize;
144    int i;
145
146    bitfieldSize = ( inf->pieceCount + 7 ) / 8;
147   
148    if( len != bitfieldSize )
149    {
150        peer_dbg( "GET  bitfield, wrong size" );
151        return TR_ERROR_ASSERT;
152    }
153
154    /* Make sure the spare bits are unset */
155    if( ( inf->pieceCount & 0x7 ) )
156    {
157        uint8_t lastByte;
158       
159        lastByte   = p[bitfieldSize-1];
160        lastByte <<= inf->pieceCount & 0x7;
161        lastByte  &= 0xFF;
162
163        if( lastByte )
164        {
165            peer_dbg( "GET  bitfield, spare bits set" );
166            return TR_ERROR_ASSERT;
167        }
168    }
169
170    peer_dbg( "GET  bitfield, ok" );
171
172    if( !peer->bitfield )
173    {
174        peer->bitfield = tr_bitfieldNew( inf->pieceCount );
175    }
176    assert( (unsigned)bitfieldSize == peer->bitfield->len );
177    memcpy( peer->bitfield->bits, p, bitfieldSize );
178
179    peer->pieceCount = 0;
180    for( i = 0; i < inf->pieceCount; i++ )
181    {
182        if( tr_bitfieldHas( peer->bitfield, i ) )
183        {
184            peer->pieceCount++;
185        }
186    }
187    peer->progress = (float) peer->pieceCount / inf->pieceCount;
188
189    updateInterest( tor, peer );
190
191    return TR_OK;
192}
193
194static inline int parseRequest( tr_torrent_t * tor, tr_peer_t * peer,
195                                uint8_t * p, int len )
196{
197    tr_info_t * inf = &tor->info;
198    int index, begin, length;
199    tr_request_t * r;
200
201    if( len != 12 )
202    {
203        peer_dbg( "GET  request, invalid" );
204        return TR_ERROR_ASSERT;
205    }
206
207    if( peer->amChoking )
208    {
209        /* Didn't he get it? */
210        sendChoke( peer, 1 );
211        return TR_OK;
212    }
213   
214    TR_NTOHL( p,     index );
215    TR_NTOHL( &p[4], begin );
216    TR_NTOHL( &p[8], length );
217
218    if( inf->pieceCount <= index )
219    {
220        peer_dbg( "GET  request, invalid index" );
221        return TR_ERROR_ASSERT;
222    }
223    if( tr_pieceSize( index ) < begin + length )
224    {
225        peer_dbg( "GET  request, invalid begin/length" );
226        return TR_ERROR_ASSERT;
227    }
228
229    peer_dbg( "GET  request %d/%d (%d bytes)",
230              index, begin, length );
231
232    /* TODO sanity checks (do we have the piece, etc) */
233
234    if( length > 16384 )
235    {
236        /* Sorry mate */
237        return TR_ERROR;
238    }
239
240    if( peer->outRequestCount >= MAX_REQUEST_COUNT )
241    {
242        tr_err( "Too many requests" );
243        return TR_ERROR;
244    }
245
246    r         = &peer->outRequests[peer->outRequestCount];
247    r->index  = index;
248    r->begin  = begin;
249    r->length = length;
250
251    (peer->outRequestCount)++;
252
253    return TR_OK;
254}
255
256static inline void updateRequests( tr_peer_t * peer, int index, int begin )
257{
258    tr_request_t * r;
259    int i;
260
261    /* Find this block in the requests list */
262    for( i = 0; i < peer->inRequestCount; i++ )
263    {
264        r = &peer->inRequests[i];
265        if( index == r->index && begin == r->begin )
266        {
267            break;
268        }
269    }
270
271    /* Usually 'i' would be 0, but some clients don't handle multiple
272       requests and drop previous requests, some other clients don't
273       send blocks in the same order we sent the requests */
274    if( i < peer->inRequestCount )
275    {
276        peer->inRequestCount--;
277        memmove( &peer->inRequests[i], &peer->inRequests[i+1],
278                 ( peer->inRequestCount - i ) * sizeof( tr_request_t ) );
279    }
280    else
281    {
282        /* Not in the list. Probably because of a cancel that arrived
283           too late */
284        peer_dbg( "wasn't expecting this block" );
285    }
286}
287
288static inline int parsePiece( tr_torrent_t * tor, tr_peer_t * peer,
289                              uint8_t * p, int len )
290{
291    tr_info_t * inf = &tor->info;
292    int index, begin, block, i, ret;
293
294    if( 8 > len )
295    {
296        peer_dbg( "GET  piece, too short (8 > %i)", len );
297        return TR_ERROR_ASSERT;
298    }
299
300    TR_NTOHL( p,     index );
301    TR_NTOHL( &p[4], begin );
302
303    if( inf->pieceCount <= index )
304    {
305        peer_dbg( "GET  piece, invalid index" );
306        return TR_ERROR_ASSERT;
307    }
308    if( tr_pieceSize( index ) < begin + len - 8 )
309    {
310        peer_dbg( "GET  piece, invalid begin/length" );
311        return TR_ERROR_ASSERT;
312    }
313
314    block = tr_block( index, begin );
315
316    peer_dbg( "GET  piece %d/%d (%d bytes)",
317              index, begin, len - 8 );
318
319    updateRequests( peer, index, begin );
320    tor->downloadedCur += len - 8;
321
322    /* Sanity checks */
323    if( len - 8 != tr_blockSize( block ) )
324    {
325        peer_dbg( "wrong size (expecting %d)", tr_blockSize( block ) );
326        return TR_ERROR_ASSERT;
327    }
328    if( tr_cpBlockIsComplete( tor->completion, block ) )
329    {
330        peer_dbg( "have this block already" );
331        return TR_OK;
332    }
333
334    /* Set blame/credit for this piece */
335    if( !peer->blamefield )
336    {
337        peer->blamefield = tr_bitfieldNew( inf->pieceCount );
338    }
339    tr_bitfieldAdd( peer->blamefield, index );
340
341    /* Write to disk */
342    if( ( ret = tr_ioWrite( tor->io, index, begin, len - 8, &p[8] ) ) )
343    {
344        return ret;
345    }
346    tr_cpBlockAdd( tor->completion, block );
347    tr_peerSentBlockToUs( peer, len-8 );
348    broadcastCancel( tor, index, begin, len - 8 );
349
350    if( !tr_cpPieceHasAllBlocks( tor->completion, index ) )
351    {
352        return TR_OK;
353    }
354
355    /* Piece is complete, check it */
356    if( ( ret = tr_ioHash( tor->io, index ) ) )
357    {
358        return ret;
359    }
360    if( !tr_cpPieceIsComplete( tor->completion, index ) )
361    {
362        return TR_OK;
363    }
364
365    /* Hash OK */
366    for( i = 0; i < tor->peerCount; i++ )
367    {
368        tr_peer_t * otherPeer;
369        otherPeer = tor->peers[i];
370
371        if( otherPeer->status < PEER_STATUS_CONNECTED )
372            continue;
373
374        sendHave( otherPeer, index );
375        updateInterest( tor, otherPeer );
376    }
377
378    return TR_OK;
379}
380
381static inline int parseCancel( tr_torrent_t * tor, tr_peer_t * peer,
382                               uint8_t * p, int len )
383{
384    tr_info_t * inf = &tor->info;
385    int index, begin, length;
386    int i;
387    tr_request_t * r;
388
389    if( len != 12 )
390    {
391        peer_dbg( "GET  cancel, invalid" );
392        return TR_ERROR_ASSERT;
393    }
394
395    TR_NTOHL( p,     index );
396    TR_NTOHL( &p[4], begin );
397    TR_NTOHL( &p[8], length );
398
399    if( inf->pieceCount <= index )
400    {
401        peer_dbg( "GET  cancel, invalid index" );
402        return TR_ERROR_ASSERT;
403    }
404    if( tr_pieceSize( index ) < begin + length )
405    {
406        peer_dbg( "GET  cancel, invalid begin/length" );
407        return TR_ERROR_ASSERT;
408    }
409
410    peer_dbg( "GET  cancel %d/%d (%d bytes)",
411              index, begin, length );
412
413    for( i = 0; i < peer->outRequestCount; i++ )
414    {
415        r = &peer->outRequests[i];
416        if( r->index == index && r->begin == begin &&
417            r->length == length )
418        {
419            (peer->outRequestCount)--;
420            memmove( &r[0], &r[1], sizeof( tr_request_t ) *
421                    ( peer->outRequestCount - i ) );
422            break;
423        }
424    }
425
426    return TR_OK;
427}
428
429static inline int parsePort( tr_peer_t * peer, uint8_t * p, int len )
430{
431    in_port_t port;
432
433    if( len != 2 )
434    {
435        peer_dbg( "GET  port, invalid" );
436        return TR_ERROR_ASSERT;
437    }
438
439    port = *( (in_port_t *) p );
440    peer_dbg( "GET  port %d", ntohs( port ) );
441
442    return TR_OK;
443}
444
445static inline int
446parseMessageHeader( tr_peer_t * peer, uint8_t * buf, int buflen,
447                    int * msgid, int * msglen )
448{
449    if( 4 > buflen )
450    {
451        return TR_NET_BLOCK;
452    }
453
454    /* Get payload size */
455    TR_NTOHL( buf, *msglen );
456
457    if( 4 + *msglen > buflen )
458    {
459        /* We do not have the entire message */
460        return TR_NET_BLOCK;
461    }
462
463    if( 0 == *msglen )
464    {
465        /* keep-alive */
466        peer_dbg( "GET  keep-alive" );
467        *msgid = AZ_MSG_BT_KEEP_ALIVE;
468        return 4;
469    }
470    else
471    {
472        /* Type of the message */
473        *msgid = buf[4];
474        (*msglen)--;
475        return 5;
476    }
477}
478
479static inline int parseMessage( tr_torrent_t * tor, tr_peer_t * peer,
480                                int id, uint8_t * p, int len )
481{
482    int extid;
483
484    switch( id )
485    {
486        case PEER_MSG_CHOKE:
487            return parseChoke( tor, peer, len, 1 );
488        case PEER_MSG_UNCHOKE:
489            return parseChoke( tor, peer, len, 0 );
490        case PEER_MSG_INTERESTED:
491            return parseInterested( peer, len, 1 );
492        case PEER_MSG_UNINTERESTED:
493            return parseInterested( peer, len, 0 );
494        case PEER_MSG_HAVE:
495            return parseHave( tor, peer, p, len );
496        case PEER_MSG_BITFIELD:
497            return parseBitfield( tor, peer, p, len );
498        case PEER_MSG_REQUEST:
499            return parseRequest( tor, peer, p, len );
500        case PEER_MSG_PIECE:
501            return parsePiece( tor, peer, p, len );
502        case PEER_MSG_CANCEL:
503            return parseCancel( tor, peer, p, len );
504        case PEER_MSG_PORT:
505            return parsePort( peer, p, len );
506        case PEER_MSG_EXTENDED:
507            if( EXTENDED_NOT_SUPPORTED == peer->extStatus )
508            {
509                break;
510            }
511            if( 0 < len )
512            {
513                extid = p[0];
514                p++;
515                len--;
516                if( EXTENDED_HANDSHAKE_ID == extid )
517                {
518                    return parseExtendedHandshake( peer, p, len );
519                }
520                else if( 0 < peer->pexStatus && extid == peer->pexStatus )
521                {
522                    return parseUTPex( tor, peer, p, len );
523                }
524                peer_dbg( "GET  unknown extended message '%hhu'", extid );
525            }
526            return 1;
527        case AZ_MSG_BT_KEEP_ALIVE:
528            return TR_OK;
529        case AZ_MSG_AZ_PEER_EXCHANGE:
530            if( peer->azproto && peer->pexStatus )
531            {
532                return parseAZPex( tor, peer, p, len );
533            }
534            break;
535        case AZ_MSG_INVALID:
536            return 0;
537    }
538
539    peer_dbg( "GET  unknown message '%d'", id );
540    return TR_ERROR;
541}
542
543static inline int parseBufHeader( tr_peer_t * peer )
544{
545    static uint8_t badproto_http[] =
546"HTTP/1.0 400 Nice try...\015\012"
547"Content-type: text/plain\015\012"
548"\015\012";
549    static uint8_t badproto_tinfoil[] =
550"This is not a rootkit or other backdoor, it's a BitTorrent\015\012"
551"client. Really. Why should you be worried, can't you read this\015\012"
552"reassuring message? Now just listen to this social engi, er, I mean,\015\012"
553"completely truthful statement, and go about your business. Your box is\015\012"
554"safe and completely impregnable, the marketing hype for your OS even\015\012"
555"says so. You can believe everything you read. Now move along, nothing\015\012"
556"to see here.";
557    uint8_t * p   = peer->buf;
558
559    if( 4 > peer->pos )
560    {
561        return TR_OK;
562    }
563
564    if( p[0] != 19 || memcmp( &p[1], "Bit", 3 ) )
565    {
566        /* Don't wait until we get 68 bytes, this is wrong
567           already */
568        peer_dbg( "GET  handshake, invalid" );
569        if( 0 == memcmp( p, "GET ", 4 ) || 0 == memcmp( p, "HEAD", 4 ) )
570        {
571            tr_netSend( peer->socket, badproto_http, sizeof badproto_http - 1 );
572        }
573        tr_netSend( peer->socket, badproto_tinfoil, sizeof badproto_tinfoil - 1 );
574        return TR_ERROR;
575    }
576    if( peer->pos < 68 )
577    {
578        return TR_OK;
579    }
580    if( memcmp( &p[4], "Torrent protocol", 16 ) )
581    {
582        peer_dbg( "GET  handshake, invalid" );
583        return TR_ERROR;
584    }
585
586    return TR_OK;
587}
588
589static const uint8_t * parseBufHash( const tr_peer_t * peer )
590{
591    if( 48 > peer->pos )
592    {
593        return NULL;
594    }
595    else
596    {
597        return peer->buf + 28;
598    }
599}
600
601static inline int parseHandshake( tr_torrent_t * tor, tr_peer_t * peer )
602{
603    tr_info_t * inf = &tor->info;
604    int         ii;
605
606    if( memcmp( &peer->buf[28], inf->hash, SHA_DIGEST_LENGTH ) )
607    {
608        peer_dbg( "GET  handshake, wrong torrent hash" );
609        return TR_ERROR;
610    }
611
612    if( !memcmp( &peer->buf[48], tor->id, TR_ID_LEN ) )
613    {
614        /* We are connected to ourselves... */
615        peer_dbg( "GET  handshake, that is us" );
616        return TR_ERROR;
617    }
618
619    memcpy( peer->id, &peer->buf[48], TR_ID_LEN );
620
621    for( ii = 0; ii < tor->peerCount; ii++ )
622    {
623        if( tor->peers[ii] == peer )
624        {
625            continue;
626        }
627        if( !peerCmp( peer, tor->peers[ii] ) )
628        {
629            peer_dbg( "GET  handshake, duplicate" );
630            return TR_ERROR;
631        }
632    }
633
634    if( PEER_SUPPORTS_EXTENDED_MESSAGES( &peer->buf[20] ) )
635    {
636        peer->status = PEER_STATUS_CONNECTED;
637        peer->extStatus = EXTENDED_SUPPORTED;
638        peer_dbg( "GET  handshake, ok (%s) extended messaging supported",
639                  tr_peerClient( peer ) );
640    }
641    else if( PEER_SUPPORTS_AZUREUS_PROTOCOL( &peer->buf[20] ) )
642    {
643        peer->status  = PEER_STATUS_AZ_GIVER;
644        peer->azproto = 1;
645        peer->date    = tr_date();
646        peer_dbg( "GET  handshake, ok (%s) will use azureus protocol",
647                  tr_peerClient( peer ) );
648    }
649    else
650    {
651        peer->status = PEER_STATUS_CONNECTED;
652        peer_dbg( "GET  handshake, ok (%s)", tr_peerClient( peer ) );
653    }
654
655    return TR_OK;
656}
657
658static inline int sendInitial( tr_torrent_t * tor, tr_peer_t * peer )
659{
660    if( PEER_STATUS_CONNECTED != peer->status )
661    {
662        return TR_OK;
663    }
664
665    if( EXTENDED_SUPPORTED == peer->extStatus )
666    {
667        if( sendExtended( tor, peer, EXTENDED_HANDSHAKE_ID ) )
668        {
669            return TR_ERROR;
670        }
671        peer->extStatus = EXTENDED_HANDSHAKE;
672    }
673
674    sendBitfield( tor, peer );
675
676    return TR_OK;
677}
678
679static inline int parseBuf( tr_torrent_t * tor, tr_peer_t * peer )
680{
681    int       len, ret, msgid;
682    uint8_t * buf;
683
684    buf = peer->buf;
685
686    if( peer->banned )
687    {
688        /* Don't even parse, we only stay connected */
689        peer->pos = 0;
690        return TR_OK;
691    }
692
693    while( peer->pos >= 4 )
694    {
695        if( PEER_STATUS_HANDSHAKE == peer->status )
696        {
697            ret = parseBufHeader( peer );
698            if( ret )
699            {
700                return ret;
701            }
702
703            if( peer->pos < 68 )
704            {
705                break;
706            }
707
708            ret = parseHandshake( tor, peer );
709            if( 0 > ret )
710            {
711                return ret;
712            }
713            buf       += 68;
714            peer->pos -= 68;
715
716            ret = sendInitial( tor, peer );
717            if( ret )
718            {
719                return ret;
720            }
721
722            continue;
723        }
724
725        if( PEER_STATUS_AZ_RECEIVER == peer->status )
726        {
727            ret = parseAZMessageHeader( peer, buf, peer->pos, &msgid, &len );
728            if( TR_NET_BLOCK & ret )
729            {
730                break;
731            }
732            else if( TR_NET_CLOSE & ret )
733            {
734                return TR_ERROR;
735            }
736
737            buf       += ret;
738            peer->pos -= ret;
739            assert( len <= peer->pos );
740            if( AZ_MSG_AZ_HANDSHAKE != msgid ||
741                parseAZHandshake( peer, buf, len ) )
742            {
743                return TR_ERROR;
744            }
745            buf         += len;
746            peer->pos   -= len;
747            assert( 0 <= peer->pos );
748            peer->status = PEER_STATUS_CONNECTED;
749
750            ret = sendInitial( tor, peer );
751            if( ret )
752            {
753                return ret;
754            }
755
756            continue;
757        }
758
759        if( PEER_STATUS_CONNECTED != peer->status )
760        {
761            break;
762        }
763
764        if( peer->azproto )
765        {
766            ret = parseAZMessageHeader( peer, buf, peer->pos, &msgid, &len );
767        }
768        else
769        {
770            ret = parseMessageHeader( peer, buf, peer->pos, &msgid, &len );
771        }
772        if( TR_NET_BLOCK & ret )
773        {
774            break;
775        }
776        else if( TR_NET_CLOSE & ret )
777        {
778            return TR_ERROR;
779        }
780
781#if 0
782        if( len > 8 + tor->blockSize )
783        {
784            /* This should never happen. Drop that peer */
785            /* XXX could an extended message be longer than this? */
786            peer_dbg( "message too large (%d bytes)", len );
787            return TR_ERROR;
788        }
789#endif
790
791        buf       += ret;
792        peer->pos -= ret;
793        assert( 0 <= peer->pos );
794
795        if( ( ret = parseMessage( tor, peer, msgid, buf, len ) ) )
796        {
797            return ret;
798        }
799
800        buf       += len;
801        peer->pos -= len;
802        assert( 0 <= peer->pos );
803    }
804
805    if( 0 < peer->pos )
806    {
807        memmove( peer->buf, buf, peer->pos );
808    }
809
810    return TR_OK;
811}
Note: See TracBrowser for help on using the repository browser.