/******************************************************************************
 * $Id: peerparse.h 261 2006-05-29 21:27:31Z titer $
 *
 * 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 );
    }
    tr_bitfieldAdd( peer->bitfield, piece );
    updateInterest( tor, peer );

    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;

    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 );
    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->downloaded += 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;
}
