transmission/libtransmission/peerparse.h

584 lines
14 KiB
C

/******************************************************************************
* $Id$
*
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
/***********************************************************************
* This file handles all incoming messages
**********************************************************************/
/***********************************************************************
* parseChoke
***********************************************************************
*
**********************************************************************/
static inline int parseChoke( tr_torrent_t * tor, tr_peer_t * peer,
int len, int choking )
{
tr_request_t * r;
int i;
if( len != 1 )
{
peer_dbg( "GET %schoke, invalid", choking ? "" : "un" );
return 1;
}
peer_dbg( "GET %schoke", choking ? "" : "un" );
peer->peerChoking = choking;
if( choking )
{
/* Discard all pending requests */
for( i = 0; i < peer->inRequestCount; i++ )
{
r = &peer->inRequests[i];
tr_cpDownloaderRem( tor->completion, tr_block(r->index,r->begin) );
}
peer->inRequestCount = 0;
}
return 0;
}
/***********************************************************************
* parseInterested
***********************************************************************
*
**********************************************************************/
static inline int parseInterested( tr_peer_t * peer, int len,
int interested )
{
if( len != 1 )
{
peer_dbg( "GET %sinterested, invalid", interested ? "" : "un" );
return 1;
}
peer_dbg( "GET %sinterested", interested ? "" : "un" );
peer->peerInterested = interested;
return 0;
}
/***********************************************************************
* parseHave
***********************************************************************
*
**********************************************************************/
static inline int parseHave( tr_torrent_t * tor, tr_peer_t * peer,
uint8_t * p, int len )
{
uint32_t piece;
if( len != 5 )
{
peer_dbg( "GET have, invalid" );
return 1;
}
TR_NTOHL( p, piece );
peer_dbg( "GET have %d", piece );
if( !peer->bitfield )
{
peer->bitfield = calloc( ( tor->info.pieceCount + 7 ) / 8, 1 );
}
if( !tr_bitfieldHas( peer->bitfield, piece ) )
{
peer->pieceCount++;
peer->progress = (float) peer->pieceCount / tor->info.pieceCount;
}
tr_bitfieldAdd( peer->bitfield, piece );
updateInterest( tor, peer );
tr_rcTransferred( tor->swarmspeed, tor->info.pieceSize );
return 0;
}
static inline int parseBitfield( tr_torrent_t * tor, tr_peer_t * peer,
uint8_t * p, int len )
{
tr_info_t * inf = &tor->info;
int bitfieldSize;
int i;
bitfieldSize = ( inf->pieceCount + 7 ) / 8;
if( len != 1 + bitfieldSize )
{
peer_dbg( "GET bitfield, wrong size" );
return 1;
}
/* Make sure the spare bits are unset */
if( ( inf->pieceCount & 0x7 ) )
{
uint8_t lastByte;
lastByte = p[bitfieldSize-1];
lastByte <<= inf->pieceCount & 0x7;
lastByte &= 0xFF;
if( lastByte )
{
peer_dbg( "GET bitfield, spare bits set" );
return 1;
}
}
peer_dbg( "GET bitfield, ok" );
if( !peer->bitfield )
{
peer->bitfield = malloc( bitfieldSize );
}
memcpy( peer->bitfield, p, bitfieldSize );
peer->pieceCount = 0;
for( i = 0; i < inf->pieceCount; i++ )
{
if( tr_bitfieldHas( peer->bitfield, i ) )
{
peer->pieceCount++;
}
}
peer->progress = (float) peer->pieceCount / inf->pieceCount;
updateInterest( tor, peer );
return 0;
}
static inline int parseRequest( tr_peer_t * peer, uint8_t * p, int len )
{
int index, begin, length;
tr_request_t * r;
if( len != 13 )
{
peer_dbg( "GET request, invalid" );
return 1;
}
if( peer->amChoking )
{
/* Didn't he get it? */
sendChoke( peer, 1 );
return 0;
}
TR_NTOHL( p, index );
TR_NTOHL( &p[4], begin );
TR_NTOHL( &p[8], length );
peer_dbg( "GET request %d/%d (%d bytes)",
index, begin, length );
/* TODO sanity checks (do we have the piece, etc) */
if( length > 16384 )
{
/* Sorry mate */
return 1;
}
if( peer->outRequestCount >= MAX_REQUEST_COUNT )
{
tr_err( "Too many requests" );
return 1;
}
r = &peer->outRequests[peer->outRequestCount];
r->index = index;
r->begin = begin;
r->length = length;
(peer->outRequestCount)++;
return 0;
}
static inline int parsePiece( tr_torrent_t * tor, tr_peer_t * peer,
uint8_t * p, int len )
{
int index, begin, block, i, j;
tr_request_t * r;
TR_NTOHL( p, index );
TR_NTOHL( &p[4], begin );
peer_dbg( "GET piece %d/%d (%d bytes)",
index, begin, len - 9 );
if( peer->inRequestCount < 1 )
{
/* Our "cancel" was probably late */
peer_dbg( "not expecting a block" );
return 0;
}
r = &peer->inRequests[0];
if( index != r->index || begin != r->begin )
{
int suckyClient;
/* Either our "cancel" was late, or this is a sucky
client that cannot deal with multiple requests */
suckyClient = 0;
for( i = 0; i < peer->inRequestCount; i++ )
{
r = &peer->inRequests[i];
if( index != r->index || begin != r->begin )
{
continue;
}
/* Sucky client, he dropped the previous requests */
peer_dbg( "block was expected later" );
for( j = 0; j < i; j++ )
{
r = &peer->inRequests[j];
tr_cpDownloaderRem( tor->completion,
tr_block(r->index,r->begin) );
}
suckyClient = 1;
peer->inRequestCount -= i;
memmove( &peer->inRequests[0], &peer->inRequests[i],
peer->inRequestCount * sizeof( tr_request_t ) );
r = &peer->inRequests[0];
break;
}
if( !suckyClient )
{
r = &peer->inRequests[0];
peer_dbg( "wrong block (expecting %d/%d)",
r->index, r->begin );
return 0;
}
}
if( len - 9 != r->length )
{
peer_dbg( "wrong size (expecting %d)", r->length );
return 1;
}
tor->downloadedCur += r->length;
block = tr_block( r->index, r->begin );
if( tr_cpBlockIsComplete( tor->completion, block ) )
{
peer_dbg( "have this block already" );
(peer->inRequestCount)--;
memmove( &peer->inRequests[0], &peer->inRequests[1],
peer->inRequestCount * sizeof( tr_request_t ) );
return 0;
}
/* set blame/credit for this piece */
if( !peer->blamefield )
{
peer->blamefield = calloc( ( tor->info.pieceCount + 7 ) / 8, 1 );
}
tr_bitfieldAdd( peer->blamefield, index );
tr_cpBlockAdd( tor->completion, block );
tr_ioWrite( tor->io, index, begin, len - 9, &p[8] );
tr_cpDownloaderRem( tor->completion, block );
sendCancel( tor, block );
if( tr_cpPieceIsComplete( tor->completion, index ) )
{
tr_peer_t * otherPeer;
for( i = 0; i < tor->peerCount; i++ )
{
otherPeer = tor->peers[i];
if( otherPeer->status < PEER_STATUS_CONNECTED )
{
continue;
}
sendHave( otherPeer, index );
updateInterest( tor, otherPeer );
}
}
(peer->inRequestCount)--;
memmove( &peer->inRequests[0], &peer->inRequests[1],
peer->inRequestCount * sizeof( tr_request_t ) );
return 0;
}
static inline int parseCancel( tr_peer_t * peer, uint8_t * p, int len )
{
int index, begin, length;
int i;
tr_request_t * r;
if( len != 13 )
{
peer_dbg( "GET cancel, invalid" );
return 1;
}
TR_NTOHL( p, index );
TR_NTOHL( &p[4], begin );
TR_NTOHL( &p[8], length );
peer_dbg( "GET cancel %d/%d (%d bytes)",
index, begin, length );
for( i = 0; i < peer->outRequestCount; i++ )
{
r = &peer->outRequests[i];
if( r->index == index && r->begin == begin &&
r->length == length )
{
(peer->outRequestCount)--;
memmove( &r[0], &r[1], sizeof( tr_request_t ) *
( peer->outRequestCount - i ) );
break;
}
}
return 0;
}
static inline int parsePort( tr_peer_t * peer, uint8_t * p, int len )
{
in_port_t port;
if( len != 3 )
{
peer_dbg( "GET port, invalid" );
return 1;
}
port = *( (in_port_t *) p );
peer_dbg( "GET port %d", ntohs( port ) );
return 0;
}
static inline int parseMessage( tr_torrent_t * tor, tr_peer_t * peer,
uint8_t * p, int len )
{
char id;
/* Type of the message */
id = *(p++);
switch( id )
{
case 0:
return parseChoke( tor, peer, len, 1 );
case 1:
return parseChoke( tor, peer, len, 0 );
case 2:
return parseInterested( peer, len, 1 );
case 3:
return parseInterested( peer, len, 0 );
case 4:
return parseHave( tor, peer, p, len );
case 5:
return parseBitfield( tor, peer, p, len );
case 6:
return parseRequest( peer, p, len );
case 7:
return parsePiece( tor, peer, p, len );
case 8:
return parseCancel( peer, p, len );
case 9:
return parsePort( peer, p, len );
}
peer_dbg( "Unknown message '%d'", id );
return 1;
}
static inline int parseBufHeader( tr_peer_t * peer )
{
uint8_t * p = peer->buf;
if( 4 > peer->pos )
{
return 0;
}
if( p[0] != 19 || memcmp( &p[1], "Bit", 3 ) )
{
/* Don't wait until we get 68 bytes, this is wrong
already */
peer_dbg( "GET handshake, invalid" );
tr_netSend( peer->socket, (uint8_t *) "Nice try...\r\n", 13 );
return 1;
}
if( peer->pos < 68 )
{
return 0;
}
if( memcmp( &p[4], "Torrent protocol", 16 ) )
{
peer_dbg( "GET handshake, invalid" );
return 1;
}
return 0;
}
static uint8_t * parseBufHash( tr_peer_t * peer )
{
if( 48 > peer->pos )
{
return NULL;
}
else
{
return peer->buf + 28;
}
}
static inline int parseBuf( tr_torrent_t * tor, tr_peer_t * peer )
{
tr_info_t * inf = &tor->info;
int i;
int len;
uint8_t * p = peer->buf;
uint8_t * end = &p[peer->pos];
if( peer->banned )
{
/* Don't even parse, we only stay connected */
peer->pos = 0;
return 0;
}
while( peer->pos >= 4 )
{
if( peer->status & PEER_STATUS_HANDSHAKE )
{
char * client;
if( parseBufHeader( peer ) )
{
return 1;
}
if( peer->pos < 68 )
{
break;
}
if( memcmp( &p[28], inf->hash, 20 ) )
{
peer_dbg( "GET handshake, wrong torrent hash" );
return 1;
}
if( !memcmp( &p[48], tor->id, 20 ) )
{
/* We are connected to ourselves... */
peer_dbg( "GET handshake, that is us" );
return 1;
}
peer->status = PEER_STATUS_CONNECTED;
memcpy( peer->id, &p[48], 20 );
p += 68;
peer->pos -= 68;
for( i = 0; i < tor->peerCount; i++ )
{
if( tor->peers[i] == peer )
{
continue;
}
if( !peerCmp( peer, tor->peers[i] ) )
{
peer_dbg( "GET handshake, duplicate" );
return 1;
}
}
client = tr_clientForId( (uint8_t *) peer->id );
peer_dbg( "GET handshake, ok (%s)", client );
free( client );
sendBitfield( tor, peer );
continue;
}
/* Get payload size */
TR_NTOHL( p, len );
p += 4;
if( len > 9 + tor->blockSize )
{
/* This should never happen. Drop that peer */
peer_dbg( "message too large (%d bytes)", len );
return 1;
}
if( !len )
{
/* keep-alive */
peer_dbg( "GET keep-alive" );
peer->pos -= 4;
continue;
}
if( &p[len] > end )
{
/* We do not have the entire message */
p -= 4;
break;
}
/* Remaining data after this message */
peer->pos -= 4 + len;
if( parseMessage( tor, peer, p, len ) )
{
return 1;
}
p += len;
}
memmove( peer->buf, p, peer->pos );
return 0;
}