source: trunk/libtransmission/peerparse.h @ 2531

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

a nice patch that makes the three most CPU-hogging functions (as reported by Shark) essentially free and reduces memory overhead by 8 bytes per piece per torrent. Lots of asserts for extra testing fun.

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