source: trunk/libtransmission/peerparse.h @ 2310

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

fix r2306 double-free crash reported by Gimp_, webaake

  • Property svn:keywords set to Date Rev Author Id
File size: 21.3 KB
Line 
1/******************************************************************************
2 * $Id: peerparse.h 2310 2007-07-09 16:30:20Z 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    r = tr_new0( tr_request_t, 1 );
241    r->index = index;
242    r->begin = begin;
243    r->length = length;
244    peer->outRequests = tr_list_append( peer->outRequests, r );
245
246    return TR_OK;
247}
248
249static inline void updateRequests( tr_peer_t * peer, int index, int begin )
250{
251    tr_request_t * r;
252    int i;
253
254    /* Find this block in the requests list */
255    for( i = 0; i < peer->inRequestCount; i++ )
256    {
257        r = &peer->inRequests[i];
258        if( index == r->index && begin == r->begin )
259        {
260            break;
261        }
262    }
263
264    /* Usually 'i' would be 0, but some clients don't handle multiple
265       requests and drop previous requests, some other clients don't
266       send blocks in the same order we sent the requests */
267    if( i < peer->inRequestCount )
268    {
269        peer->inRequestCount--;
270        memmove( &peer->inRequests[i], &peer->inRequests[i+1],
271                 ( peer->inRequestCount - i ) * sizeof( tr_request_t ) );
272    }
273    else
274    {
275        /* Not in the list. Probably because of a cancel that arrived
276           too late */
277        peer_dbg( "wasn't expecting this block" );
278    }
279}
280
281static inline int parsePiece( tr_torrent_t * tor, tr_peer_t * peer,
282                              uint8_t * p, int len )
283{
284    tr_info_t * inf = &tor->info;
285    int index, begin, block, i, ret;
286
287    if( 8 > len )
288    {
289        peer_dbg( "GET  piece, too short (8 > %i)", len );
290        return TR_ERROR_ASSERT;
291    }
292
293    TR_NTOHL( p,     index );
294    TR_NTOHL( &p[4], begin );
295
296    if( inf->pieceCount <= index )
297    {
298        peer_dbg( "GET  piece, invalid index" );
299        return TR_ERROR_ASSERT;
300    }
301    if( tr_pieceSize( index ) < begin + len - 8 )
302    {
303        peer_dbg( "GET  piece, invalid begin/length" );
304        return TR_ERROR_ASSERT;
305    }
306
307    block = tr_block( index, begin );
308
309    peer_dbg( "GET  piece %d/%d (%d bytes)",
310              index, begin, len - 8 );
311
312    updateRequests( peer, index, begin );
313    tor->downloadedCur += len - 8;
314
315    /* Sanity checks */
316    if( len - 8 != tr_blockSize( block ) )
317    {
318        peer_dbg( "wrong size (expecting %d)", tr_blockSize( block ) );
319        return TR_ERROR_ASSERT;
320    }
321    if( tr_cpBlockIsComplete( tor->completion, block ) )
322    {
323        peer_dbg( "have this block already" );
324        return TR_OK;
325    }
326
327    /* Set blame/credit for this piece */
328    if( !peer->blamefield )
329    {
330        peer->blamefield = tr_bitfieldNew( inf->pieceCount );
331    }
332    tr_bitfieldAdd( peer->blamefield, index );
333
334    /* Write to disk */
335    if( ( ret = tr_ioWrite( tor->io, index, begin, len - 8, &p[8] ) ) )
336    {
337        return ret;
338    }
339    tr_cpBlockAdd( tor->completion, block );
340    tr_peerSentBlockToUs( peer, len-8 );
341    broadcastCancel( tor, index, begin, len - 8 );
342
343    if( !tr_cpPieceHasAllBlocks( tor->completion, index ) )
344    {
345        return TR_OK;
346    }
347
348    /* Piece is complete, check it */
349    if( ( ret = tr_ioHash( tor->io, index ) ) )
350    {
351        return ret;
352    }
353    if( !tr_cpPieceIsComplete( tor->completion, index ) )
354    {
355        return TR_OK;
356    }
357
358    /* Hash OK */
359    for( i = 0; i < tor->peerCount; i++ )
360    {
361        tr_peer_t * otherPeer;
362        otherPeer = tor->peers[i];
363
364        if( otherPeer->status < PEER_STATUS_CONNECTED )
365            continue;
366
367        sendHave( otherPeer, index );
368        updateInterest( tor, otherPeer );
369    }
370
371    return TR_OK;
372}
373
374static int reqCompare( const void * va, const void * vb )
375{
376    const tr_request_t * a = (const tr_request_t *) va;
377    const tr_request_t * b = (const tr_request_t *) vb;
378    if( a->index != b->index ) return a->index - b->index;
379    if( a->begin != b->begin ) return a->begin - b->begin;
380    return a->length - b->length;
381}
382
383static inline int parseCancel( tr_torrent_t * tor, tr_peer_t * peer,
384                               uint8_t * p, int len )
385{
386    tr_info_t * inf = &tor->info;
387    int index, begin, length;
388    tr_request_t req;
389    tr_list_t * l;
390
391    if( len != 12 )
392    {
393        peer_dbg( "GET  cancel, invalid" );
394        return TR_ERROR_ASSERT;
395    }
396
397    TR_NTOHL( p,     index );
398    TR_NTOHL( &p[4], begin );
399    TR_NTOHL( &p[8], length );
400
401    if( inf->pieceCount <= index )
402    {
403        peer_dbg( "GET  cancel, invalid index" );
404        return TR_ERROR_ASSERT;
405    }
406    if( tr_pieceSize( index ) < begin + length )
407    {
408        peer_dbg( "GET  cancel, invalid begin/length" );
409        return TR_ERROR_ASSERT;
410    }
411
412    peer_dbg( "GET  cancel %d/%d (%d bytes)",
413              index, begin, length );
414
415    /* remove it from the outRequests list */
416    req.index = index;
417    req.begin = begin;
418    req.length = length;
419    while(( l = tr_list_find( peer->outRequests, reqCompare, &req ) )) {
420        tr_request_t * r = (tr_request_t *) l->data;
421        peer->outRequests = tr_list_remove_data( peer->outRequests, r );
422        tr_free( r );
423    }
424
425    return TR_OK;
426}
427
428static inline int parsePort( tr_peer_t * peer, uint8_t * p, int len )
429{
430    in_port_t port;
431
432    if( len != 2 )
433    {
434        peer_dbg( "GET  port, invalid" );
435        return TR_ERROR_ASSERT;
436    }
437
438    port = *( (in_port_t *) p );
439    peer_dbg( "GET  port %d", ntohs( port ) );
440
441    return TR_OK;
442}
443
444static inline int
445parseMessageHeader( tr_peer_t * peer, uint8_t * buf, int buflen,
446                    int * msgid, int * msglen )
447{
448    if( 4 > buflen )
449    {
450        return TR_NET_BLOCK;
451    }
452
453    /* Get payload size */
454    TR_NTOHL( buf, *msglen );
455
456    if( 4 + *msglen > buflen )
457    {
458        /* We do not have the entire message */
459        return TR_NET_BLOCK;
460    }
461
462    if( 0 == *msglen )
463    {
464        /* keep-alive */
465        peer_dbg( "GET  keep-alive" );
466        *msgid = AZ_MSG_BT_KEEP_ALIVE;
467        return 4;
468    }
469    else
470    {
471        /* Type of the message */
472        *msgid = buf[4];
473        (*msglen)--;
474        return 5;
475    }
476}
477
478static inline int parseMessage( tr_torrent_t * tor, tr_peer_t * peer,
479                                int id, uint8_t * p, int len )
480{
481    int extid;
482
483    switch( id )
484    {
485        case PEER_MSG_CHOKE:
486            return parseChoke( tor, peer, len, 1 );
487        case PEER_MSG_UNCHOKE:
488            return parseChoke( tor, peer, len, 0 );
489        case PEER_MSG_INTERESTED:
490            return parseInterested( peer, len, 1 );
491        case PEER_MSG_UNINTERESTED:
492            return parseInterested( peer, len, 0 );
493        case PEER_MSG_HAVE:
494            return parseHave( tor, peer, p, len );
495        case PEER_MSG_BITFIELD:
496            return parseBitfield( tor, peer, p, len );
497        case PEER_MSG_REQUEST:
498            return parseRequest( tor, peer, p, len );
499        case PEER_MSG_PIECE:
500            return parsePiece( tor, peer, p, len );
501        case PEER_MSG_CANCEL:
502            return parseCancel( tor, peer, p, len );
503        case PEER_MSG_PORT:
504            return parsePort( peer, p, len );
505        case PEER_MSG_EXTENDED:
506            if( EXTENDED_NOT_SUPPORTED == peer->extStatus )
507            {
508                break;
509            }
510            if( 0 < len )
511            {
512                extid = p[0];
513                p++;
514                len--;
515                if( EXTENDED_HANDSHAKE_ID == extid )
516                {
517                    return parseExtendedHandshake( peer, p, len );
518                }
519                else if( 0 < peer->pexStatus && extid == peer->pexStatus )
520                {
521                    return parseUTPex( tor, peer, p, len );
522                }
523                peer_dbg( "GET  unknown extended message '%hhu'", extid );
524            }
525            /* ignore the unknown extension */
526            return 0;
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    tr_err( "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.