/*
 * This file Copyright (C) Mnemosyne LLC
 *
 * This file is licensed by the GPL version 2. Works owned by the
 * Transmission project are granted a special exemption to clause 2(b)
 * so that the bulk of its code can remain under the MIT license.
 * This exemption does not extend to derived works not owned by
 * the Transmission project.
 *
 * $Id: handshake.c 12365 2011-04-17 05:22:50Z jordan $
 */

#include <assert.h>
#include <errno.h>
#include <string.h> /* strcmp(), strlen() */

#include <event2/buffer.h>
#include <event2/event.h>

#include "transmission.h"
#include "clients.h"
#include "crypto.h"
#include "handshake.h"
#include "peer-io.h"
#include "peer-mgr.h"
#include "session.h"
#include "torrent.h"
#include "tr-dht.h"
#include "utils.h"

/* enable LibTransmission extension protocol */
#define ENABLE_LTEP * /
/* fast extensions */
#define ENABLE_FAST * /
/* DHT */
#define ENABLE_DHT * /

/***
****
***/

#define HANDSHAKE_NAME "\023BitTorrent protocol"

enum
{
    /* BitTorrent Handshake Constants */
    HANDSHAKE_NAME_LEN             = 20,
    HANDSHAKE_FLAGS_LEN            = 8,
    HANDSHAKE_SIZE                 = 68,
    INCOMING_HANDSHAKE_LEN         = 48,

    /* Encryption Constants */
    PadA_MAXLEN                    = 512,
    PadB_MAXLEN                    = 512,
    PadC_MAXLEN                    = 512,
    PadD_MAXLEN                    = 512,
    VC_LENGTH                      = 8,
    CRYPTO_PROVIDE_PLAINTEXT       = 1,
    CRYPTO_PROVIDE_CRYPTO          = 2,

    /* how long to wait before giving up on a handshake */
    HANDSHAKE_TIMEOUT_SEC          = 30
};


#ifdef ENABLE_LTEP
 #define HANDSHAKE_HAS_LTEP( bits ) ( ( ( bits )[5] & 0x10 ) ? 1 : 0 )
 #define HANDSHAKE_SET_LTEP( bits ) ( ( bits )[5] |= 0x10 )
#else
 #define HANDSHAKE_HAS_LTEP( bits ) ( 0 )
 #define HANDSHAKE_SET_LTEP( bits ) ( (void)0 )
#endif

#ifdef ENABLE_FAST
 #define HANDSHAKE_HAS_FASTEXT( bits ) ( ( ( bits )[7] & 0x04 ) ? 1 : 0 )
 #define HANDSHAKE_SET_FASTEXT( bits ) ( ( bits )[7] |= 0x04 )
#else
 #define HANDSHAKE_HAS_FASTEXT( bits ) ( 0 )
 #define HANDSHAKE_SET_FASTEXT( bits ) ( (void)0 )
#endif

#ifdef ENABLE_DHT
 #define HANDSHAKE_HAS_DHT( bits ) ( ( ( bits )[7] & 0x01 ) ? 1 : 0 )
 #define HANDSHAKE_SET_DHT( bits ) ( ( bits )[7] |= 0x01 )
#else
 #define HANDSHAKE_HAS_DHT( bits ) ( 0 )
 #define HANDSHAKE_SET_DHT( bits ) ( (void)0 )
#endif

/* http://www.azureuswiki.com/index.php/Extension_negotiation_protocol
   these macros are to be used if both extended messaging and the
   azureus protocol is supported, they indicate which protocol is preferred */
#define HANDSHAKE_GET_EXTPREF( reserved )      ( ( reserved )[5] & 0x03 )
#define HANDSHAKE_SET_EXTPREF( reserved, val ) ( ( reserved )[5] |= 0x03 &\
                                                                    ( val ) )

typedef uint8_t handshake_state_t;

struct tr_handshake
{
    bool                  haveReadAnythingFromPeer;
    bool                  havePeerID;
    bool                  haveSentBitTorrentHandshake;
    tr_peerIo *           io;
    tr_crypto *           crypto;
    tr_session *          session;
    uint8_t               mySecret[KEY_LEN];
    handshake_state_t     state;
    tr_encryption_mode    encryptionMode;
    uint16_t              pad_c_len;
    uint16_t              pad_d_len;
    uint16_t              ia_len;
    uint32_t              crypto_select;
    uint32_t              crypto_provide;
    uint8_t               myReq1[SHA_DIGEST_LENGTH];
    handshakeDoneCB       doneCB;
    void *                doneUserData;
    struct event        * timeout_timer;
};

/**
***
**/

enum
{
    /* incoming */
    AWAITING_HANDSHAKE,
    AWAITING_PEER_ID,
    AWAITING_YA,
    AWAITING_PAD_A,
    AWAITING_CRYPTO_PROVIDE,
    AWAITING_PAD_C,
    AWAITING_IA,
    AWAITING_PAYLOAD_STREAM,

    /* outgoing */
    AWAITING_YB,
    AWAITING_VC,
    AWAITING_CRYPTO_SELECT,
    AWAITING_PAD_D,
};

/**
***
**/

#define dbgmsg( handshake, ... ) \
    do { \
        if( tr_deepLoggingIsActive( ) ) \
            tr_deepLog( __FILE__, __LINE__, tr_peerIoGetAddrStr( handshake->io ), __VA_ARGS__ ); \
    } while( 0 )

static const char*
getStateName( const handshake_state_t state )
{
    const char * str = "f00!";

    switch( state )
    {
        case AWAITING_HANDSHAKE:
            str = "awaiting handshake"; break;

        case AWAITING_PEER_ID:
            str = "awaiting peer id"; break;

        case AWAITING_YA:
            str = "awaiting ya"; break;

        case AWAITING_PAD_A:
            str = "awaiting pad a"; break;

        case AWAITING_CRYPTO_PROVIDE:
            str = "awaiting crypto_provide"; break;

        case AWAITING_PAD_C:
            str = "awaiting pad c"; break;

        case AWAITING_IA:
            str = "awaiting ia"; break;

        case AWAITING_YB:
            str = "awaiting yb"; break;

        case AWAITING_VC:
            str = "awaiting vc"; break;

        case AWAITING_CRYPTO_SELECT:
            str = "awaiting crypto select"; break;

        case AWAITING_PAD_D:
            str = "awaiting pad d"; break;
    }
    return str;
}

static void
setState( tr_handshake * handshake, handshake_state_t state )
{
    dbgmsg( handshake, "setting to state [%s]", getStateName( state ) );
    handshake->state = state;
}

static void
setReadState( tr_handshake * handshake, handshake_state_t state )
{
    setState( handshake, state );
}

static void
buildHandshakeMessage( tr_handshake * handshake, uint8_t * buf )
{
    uint8_t          * walk = buf;
    const uint8_t    * torrentHash = tr_cryptoGetTorrentHash( handshake->crypto );
    const tr_torrent * tor = tr_torrentFindFromHash( handshake->session, torrentHash );
    const uint8_t    * peer_id = tor && *tor->peer_id ? tor->peer_id : tr_getPeerId( handshake->session );

    memcpy( walk, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN );
    walk += HANDSHAKE_NAME_LEN;
    memset( walk, 0, HANDSHAKE_FLAGS_LEN );
    HANDSHAKE_SET_LTEP( walk );
    HANDSHAKE_SET_FASTEXT( walk );

    /* Note that this doesn't depend on whether the torrent is private.
     * We don't accept DHT peers for a private torrent,
     * but we participate in the DHT regardless. */
    if( tr_dhtEnabled( handshake->session ) )
        HANDSHAKE_SET_DHT( walk );

    walk += HANDSHAKE_FLAGS_LEN;
    memcpy( walk, torrentHash, SHA_DIGEST_LENGTH );
    walk += SHA_DIGEST_LENGTH;
    memcpy( walk, peer_id, PEER_ID_LEN );
    walk += PEER_ID_LEN;

    assert( walk - buf == HANDSHAKE_SIZE );
}

static int tr_handshakeDone( tr_handshake * handshake,
                             bool           isConnected );

enum
{
    HANDSHAKE_OK,
    HANDSHAKE_ENCRYPTION_WRONG,
    HANDSHAKE_BAD_TORRENT,
    HANDSHAKE_PEER_IS_SELF,
};

static int
parseHandshake( tr_handshake *    handshake,
                struct evbuffer * inbuf )
{
    uint8_t name[HANDSHAKE_NAME_LEN];
    uint8_t reserved[HANDSHAKE_FLAGS_LEN];
    uint8_t hash[SHA_DIGEST_LENGTH];
    const tr_torrent * tor;
    const uint8_t * tor_peer_id;
    uint8_t peer_id[PEER_ID_LEN];

    dbgmsg( handshake, "payload: need %d, got %zu",
            HANDSHAKE_SIZE, evbuffer_get_length( inbuf ) );

    if( evbuffer_get_length( inbuf ) < HANDSHAKE_SIZE )
        return READ_LATER;

    /* confirm the protocol */
    tr_peerIoReadBytes( handshake->io, inbuf, name, HANDSHAKE_NAME_LEN );
    if( memcmp( name, HANDSHAKE_NAME, HANDSHAKE_NAME_LEN ) )
        return HANDSHAKE_ENCRYPTION_WRONG;

    /* read the reserved bytes */
    tr_peerIoReadBytes( handshake->io, inbuf, reserved, HANDSHAKE_FLAGS_LEN );

    /* torrent hash */
    tr_peerIoReadBytes( handshake->io, inbuf, hash, sizeof( hash ) );
    assert( tr_peerIoHasTorrentHash( handshake->io ) );
    if( !tr_torrentExists( handshake->session, hash )
      || memcmp( hash, tr_peerIoGetTorrentHash( handshake->io ),
                 SHA_DIGEST_LENGTH ) )
    {
        dbgmsg( handshake, "peer returned the wrong hash. wtf?" );
        return HANDSHAKE_BAD_TORRENT;
    }

    /* peer_id */
    tr_peerIoReadBytes( handshake->io, inbuf, peer_id, sizeof( peer_id ) );
    tr_peerIoSetPeersId( handshake->io, peer_id );

    /* peer id */
    handshake->havePeerID = true;
    dbgmsg( handshake, "peer-id is [%*.*s]", PEER_ID_LEN, PEER_ID_LEN, peer_id );

    tor = tr_torrentFindFromHash( handshake->session, hash );
    tor_peer_id = tor ? tor->peer_id : tr_getPeerId( handshake->session );
    if( !memcmp( peer_id, tor_peer_id, PEER_ID_LEN ) )
    {
        dbgmsg( handshake, "streuth!  we've connected to ourselves." );
        return HANDSHAKE_PEER_IS_SELF;
    }

    /**
    *** Extensions
    **/

    tr_peerIoEnableLTEP( handshake->io, HANDSHAKE_HAS_LTEP( reserved ) );

    tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) );

    tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( reserved ) );

    return HANDSHAKE_OK;
}

/***
****
****  OUTGOING CONNECTIONS
****
***/

/* 1 A->B: Diffie Hellman Ya, PadA */
static void
sendYa( tr_handshake * handshake )
{
    int               len;
    const uint8_t *   public_key;
    char              outbuf[ KEY_LEN + PadA_MAXLEN ], *walk=outbuf;

    /* add our public key (Ya) */
    public_key = tr_cryptoGetMyPublicKey( handshake->crypto, &len );
    assert( len == KEY_LEN );
    assert( public_key );
    memcpy( walk, public_key, len );
    walk += len;

    /* add some bullshit padding */
    len = tr_cryptoRandInt( PadA_MAXLEN );
    tr_cryptoRandBuf( walk, len );
    walk += len;

    /* send it */
    setReadState( handshake, AWAITING_YB );
    tr_peerIoWriteBytes( handshake->io, outbuf, walk - outbuf, false );
}

static uint32_t
getCryptoProvide( const tr_handshake * handshake )
{
    uint32_t provide = 0;

    switch( handshake->encryptionMode )
    {
        case TR_ENCRYPTION_REQUIRED:
        case TR_ENCRYPTION_PREFERRED:
            provide |= CRYPTO_PROVIDE_CRYPTO;
            break;

        case TR_CLEAR_PREFERRED:
            provide |= CRYPTO_PROVIDE_CRYPTO | CRYPTO_PROVIDE_PLAINTEXT;
            break;
    }

    return provide;
}

static uint32_t
getCryptoSelect( const tr_handshake * handshake,
                 uint32_t             crypto_provide )
{
    uint32_t choices[2];
    int      i, nChoices = 0;

    switch( handshake->encryptionMode )
    {
        case TR_ENCRYPTION_REQUIRED:
            choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
            break;

        case TR_ENCRYPTION_PREFERRED:
            choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
            choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT;
            break;

        case TR_CLEAR_PREFERRED:
            choices[nChoices++] = CRYPTO_PROVIDE_PLAINTEXT;
            choices[nChoices++] = CRYPTO_PROVIDE_CRYPTO;
            break;
    }

    for( i = 0; i < nChoices; ++i )
        if( crypto_provide & choices[i] )
            return choices[i];

    return 0;
}

static int
readYb( tr_handshake * handshake, struct evbuffer * inbuf )
{
    int               isEncrypted;
    const uint8_t *   secret;
    uint8_t           yb[KEY_LEN];
    struct evbuffer * outbuf;
    size_t            needlen = HANDSHAKE_NAME_LEN;

    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    isEncrypted = memcmp( evbuffer_pullup( inbuf, HANDSHAKE_NAME_LEN ), HANDSHAKE_NAME, HANDSHAKE_NAME_LEN );
    if( isEncrypted )
    {
        needlen = KEY_LEN;
        if( evbuffer_get_length( inbuf ) < needlen )
            return READ_LATER;
    }

    dbgmsg( handshake, "got a %s handshake",
           ( isEncrypted ? "encrypted" : "plaintext" ) );

    tr_peerIoSetEncryption( handshake->io, isEncrypted ? PEER_ENCRYPTION_RC4
                                                       : PEER_ENCRYPTION_NONE );
    if( !isEncrypted )
    {
        setState( handshake, AWAITING_HANDSHAKE );
        return READ_NOW;
    }

    handshake->haveReadAnythingFromPeer = true;

    /* compute the secret */
    evbuffer_remove( inbuf, yb, KEY_LEN );
    secret = tr_cryptoComputeSecret( handshake->crypto, yb );
    memcpy( handshake->mySecret, secret, KEY_LEN );

    /* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S),
     * ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */
    outbuf = evbuffer_new( );

    /* HASH('req1', S) */
    {
        uint8_t req1[SHA_DIGEST_LENGTH];
        tr_sha1( req1, "req1", 4, secret, KEY_LEN, NULL );
        evbuffer_add( outbuf, req1, SHA_DIGEST_LENGTH );
    }

    /* HASH('req2', SKEY) xor HASH('req3', S) */
    {
        int     i;
        uint8_t req2[SHA_DIGEST_LENGTH];
        uint8_t req3[SHA_DIGEST_LENGTH];
        uint8_t buf[SHA_DIGEST_LENGTH];
        tr_sha1( req2, "req2", 4,
                 tr_cryptoGetTorrentHash( handshake->crypto ),
                 SHA_DIGEST_LENGTH, NULL );
        tr_sha1( req3, "req3", 4, secret, KEY_LEN, NULL );
        for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
            buf[i] = req2[i] ^ req3[i];
        evbuffer_add( outbuf, buf, SHA_DIGEST_LENGTH );
    }

    /* ENCRYPT(VC, crypto_provide, len(PadC), PadC
     * PadC is reserved for future extensions to the handshake...
     * standard practice at this time is for it to be zero-length */
    {
        uint8_t vc[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };

        tr_peerIoWriteBuf( handshake->io, outbuf, false );
        tr_cryptoEncryptInit( handshake->crypto );
        tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_RC4 );

        evbuffer_add        ( outbuf, vc, VC_LENGTH );
        evbuffer_add_uint32 ( outbuf, getCryptoProvide( handshake ) );
        evbuffer_add_uint16 ( outbuf, 0 );
    }

    /* ENCRYPT len(IA)), ENCRYPT(IA) */
    {
        uint8_t msg[HANDSHAKE_SIZE];
        buildHandshakeMessage( handshake, msg );

        evbuffer_add_uint16 ( outbuf, sizeof( msg ) );
        evbuffer_add        ( outbuf, msg, sizeof( msg ) );

        handshake->haveSentBitTorrentHandshake = 1;
    }

    /* send it */
    tr_cryptoDecryptInit( handshake->crypto );
    setReadState( handshake, AWAITING_VC );
    tr_peerIoWriteBuf( handshake->io, outbuf, false );

    /* cleanup */
    evbuffer_free( outbuf );
    return READ_LATER;
}

static int
readVC( tr_handshake *    handshake,
        struct evbuffer * inbuf )
{
    const uint8_t key[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    const int     key_len = VC_LENGTH;
    uint8_t       tmp[VC_LENGTH];

    /* note: this works w/o having to `unwind' the buffer if
     * we read too much, but it is pretty brute-force.
     * it would be nice to make this cleaner. */
    for( ; ; )
    {
        if( evbuffer_get_length( inbuf ) < VC_LENGTH )
        {
            dbgmsg( handshake, "not enough bytes... returning read_more" );
            return READ_LATER;
        }

        memcpy( tmp, evbuffer_pullup( inbuf, key_len ), key_len );
        tr_cryptoDecryptInit( handshake->crypto );
        tr_cryptoDecrypt( handshake->crypto, key_len, tmp, tmp );
        if( !memcmp( tmp, key, key_len ) )
            break;

        evbuffer_drain( inbuf, 1 );
    }

    dbgmsg( handshake, "got it!" );
    evbuffer_drain( inbuf, key_len );
    setState( handshake, AWAITING_CRYPTO_SELECT );
    return READ_NOW;
}

static int
readCryptoSelect( tr_handshake *    handshake,
                  struct evbuffer * inbuf )
{
    uint32_t     crypto_select;
    uint16_t     pad_d_len;
    const size_t needlen = sizeof( uint32_t ) + sizeof( uint16_t );

    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    tr_peerIoReadUint32( handshake->io, inbuf, &crypto_select );
    handshake->crypto_select = crypto_select;
    dbgmsg( handshake, "crypto select is %d", (int)crypto_select );
    if( !( crypto_select & getCryptoProvide( handshake ) ) )
    {
        dbgmsg( handshake,
                "peer selected an encryption option we didn't provide" );
        return tr_handshakeDone( handshake, false );
    }

    tr_peerIoReadUint16( handshake->io, inbuf, &pad_d_len );
    dbgmsg( handshake, "pad_d_len is %d", (int)pad_d_len );

    if( pad_d_len > 512 )
    {
        dbgmsg( handshake, "encryption handshake: pad_d_len is too long" );
        return tr_handshakeDone( handshake, false );
    }

    handshake->pad_d_len = pad_d_len;

    setState( handshake, AWAITING_PAD_D );
    return READ_NOW;
}

static int
readPadD( tr_handshake *    handshake,
          struct evbuffer * inbuf )
{
    const size_t needlen = handshake->pad_d_len;

    dbgmsg( handshake, "pad d: need %zu, got %zu",
            needlen, evbuffer_get_length( inbuf ) );
    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    tr_peerIoDrain( handshake->io, inbuf, needlen );

    tr_peerIoSetEncryption( handshake->io, handshake->crypto_select );

    setState( handshake, AWAITING_HANDSHAKE );
    return READ_NOW;
}

/***
****
****  INCOMING CONNECTIONS
****
***/

static int
readHandshake( tr_handshake *    handshake,
               struct evbuffer * inbuf )
{
    uint8_t   pstrlen;
    uint8_t   pstr[20];
    uint8_t   reserved[HANDSHAKE_FLAGS_LEN];
    uint8_t   hash[SHA_DIGEST_LENGTH];

    dbgmsg( handshake, "payload: need %d, got %zu",
            INCOMING_HANDSHAKE_LEN, evbuffer_get_length( inbuf ) );

    if( evbuffer_get_length( inbuf ) < INCOMING_HANDSHAKE_LEN )
        return READ_LATER;

    handshake->haveReadAnythingFromPeer = true;

    pstrlen = evbuffer_pullup( inbuf, 1 )[0]; /* peek, don't read. We may be
                                                 handing inbuf to AWAITING_YA */

    if( pstrlen == 19 ) /* unencrypted */
    {
        tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_NONE );

        if( handshake->encryptionMode == TR_ENCRYPTION_REQUIRED )
        {
            dbgmsg( handshake,
                    "peer is unencrypted, and we're disallowing that" );
            return tr_handshakeDone( handshake, false );
        }
    }
    else /* encrypted or corrupt */
    {
        tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_RC4 );

        if( tr_peerIoIsIncoming( handshake->io ) )
        {
            dbgmsg( handshake,
                    "I think peer is sending us an encrypted handshake..." );
            setState( handshake, AWAITING_YA );
            return READ_NOW;
        }
        tr_cryptoDecrypt( handshake->crypto, 1, &pstrlen, &pstrlen );

        if( pstrlen != 19 )
        {
            dbgmsg( handshake,
                    "I think peer has sent us a corrupt handshake..." );
            return tr_handshakeDone( handshake, false );
        }
    }

    evbuffer_drain( inbuf, 1 );

    /* pstr (BitTorrent) */
    assert( pstrlen == 19 );
    tr_peerIoReadBytes( handshake->io, inbuf, pstr, pstrlen );
    pstr[pstrlen] = '\0';
    if( memcmp( pstr, "BitTorrent protocol", 19 ) )
        return tr_handshakeDone( handshake, false );

    /* reserved bytes */
    tr_peerIoReadBytes( handshake->io, inbuf, reserved, sizeof( reserved ) );

    /**
    *** Extensions
    **/

    tr_peerIoEnableLTEP( handshake->io, HANDSHAKE_HAS_LTEP( reserved ) );

    tr_peerIoEnableFEXT( handshake->io, HANDSHAKE_HAS_FASTEXT( reserved ) );

    tr_peerIoEnableDHT( handshake->io, HANDSHAKE_HAS_DHT( reserved ) );

    /* torrent hash */
    tr_peerIoReadBytes( handshake->io, inbuf, hash, sizeof( hash ) );
    if( tr_peerIoIsIncoming( handshake->io ) )
    {
        if( !tr_torrentExists( handshake->session, hash ) )
        {
            dbgmsg( handshake, "peer is trying to connect to us for a torrent we don't have." );
            return tr_handshakeDone( handshake, false );
        }
        else
        {
            assert( !tr_peerIoHasTorrentHash( handshake->io ) );
            tr_peerIoSetTorrentHash( handshake->io, hash );
        }
    }
    else /* outgoing */
    {
        assert( tr_peerIoHasTorrentHash( handshake->io ) );
        if( memcmp( hash, tr_peerIoGetTorrentHash( handshake->io ),
                    SHA_DIGEST_LENGTH ) )
        {
            dbgmsg( handshake, "peer returned the wrong hash. wtf?" );
            return tr_handshakeDone( handshake, false );
        }
    }

    /**
    ***  If it's an incoming message, we need to send a response handshake
    **/

    if( !handshake->haveSentBitTorrentHandshake )
    {
        uint8_t msg[HANDSHAKE_SIZE];
        buildHandshakeMessage( handshake, msg );
        tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), false );
        handshake->haveSentBitTorrentHandshake = 1;
    }

    setReadState( handshake, AWAITING_PEER_ID );
    return READ_NOW;
}

static int
readPeerId( tr_handshake    * handshake,
            struct evbuffer * inbuf )
{
    bool peerIsGood;
    char client[128];
    tr_torrent * tor;
    const uint8_t * tor_peer_id;
    uint8_t peer_id[PEER_ID_LEN];

    if( evbuffer_get_length( inbuf ) < PEER_ID_LEN )
        return READ_LATER;

    /* peer id */
    tr_peerIoReadBytes( handshake->io, inbuf, peer_id, PEER_ID_LEN );
    tr_peerIoSetPeersId( handshake->io, peer_id );
    handshake->havePeerID = true;
    tr_clientForId( client, sizeof( client ), peer_id );
    dbgmsg( handshake, "peer-id is [%s] ... isIncoming is %d", client,
            tr_peerIoIsIncoming( handshake->io ) );

    /* if we've somehow connected to ourselves, don't keep the connection */
    tor = tr_torrentFindFromHash( handshake->session, tr_peerIoGetTorrentHash( handshake->io ) );
    tor_peer_id = tor ? tor->peer_id : tr_getPeerId( handshake->session );
    peerIsGood = memcmp( peer_id, tor_peer_id, PEER_ID_LEN ) != 0;
    dbgmsg( handshake, "isPeerGood == %d", (int)peerIsGood );
    return tr_handshakeDone( handshake, peerIsGood );
}

static int
readYa( tr_handshake *    handshake,
        struct evbuffer * inbuf )
{
    uint8_t        ya[KEY_LEN];
    uint8_t *      walk, outbuf[KEY_LEN + PadB_MAXLEN];
    const uint8_t *myKey, *secret;
    int            len;

    dbgmsg( handshake, "in readYa... need %d, have %zu",
            KEY_LEN, evbuffer_get_length( inbuf ) );
    if( evbuffer_get_length( inbuf ) < KEY_LEN )
        return READ_LATER;

    /* read the incoming peer's public key */
    evbuffer_remove( inbuf, ya, KEY_LEN );
    secret = tr_cryptoComputeSecret( handshake->crypto, ya );
    memcpy( handshake->mySecret, secret, KEY_LEN );
    tr_sha1( handshake->myReq1, "req1", 4, secret, KEY_LEN, NULL );

    dbgmsg( handshake, "sending B->A: Diffie Hellman Yb, PadB" );
    /* send our public key to the peer */
    walk = outbuf;
    myKey = tr_cryptoGetMyPublicKey( handshake->crypto, &len );
    memcpy( walk, myKey, len );
    walk += len;
    len = tr_cryptoRandInt( PadB_MAXLEN );
    tr_cryptoRandBuf( walk, len );
    walk += len;

    setReadState( handshake, AWAITING_PAD_A );
    tr_peerIoWriteBytes( handshake->io, outbuf, walk - outbuf, false );
    return READ_NOW;
}

static int
readPadA( tr_handshake * handshake, struct evbuffer * inbuf )
{
    /* resynchronizing on HASH('req1',S) */
    struct evbuffer_ptr ptr = evbuffer_search( inbuf, (const char*)handshake->myReq1, SHA_DIGEST_LENGTH, NULL );

    if( ptr.pos != -1 ) /* match */
    {
        evbuffer_drain( inbuf, ptr.pos );
        dbgmsg( handshake, "found it... looking setting to awaiting_crypto_provide" );
        setState( handshake, AWAITING_CRYPTO_PROVIDE );
        return READ_NOW;
    }
    else
    {
        const size_t len = evbuffer_get_length( inbuf );
        if( len > SHA_DIGEST_LENGTH )
            evbuffer_drain( inbuf, len - SHA_DIGEST_LENGTH );
        return READ_LATER;
    }
}

static int
readCryptoProvide( tr_handshake *    handshake,
                   struct evbuffer * inbuf )
{
    /* HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide,
      len(PadC)) */

    int          i;
    uint8_t      vc_in[VC_LENGTH];
    uint8_t      req2[SHA_DIGEST_LENGTH];
    uint8_t      req3[SHA_DIGEST_LENGTH];
    uint8_t      obfuscatedTorrentHash[SHA_DIGEST_LENGTH];
    uint16_t     padc_len = 0;
    uint32_t     crypto_provide = 0;
    const size_t needlen = SHA_DIGEST_LENGTH /* HASH('req1',s) */
                           + SHA_DIGEST_LENGTH /* HASH('req2', SKEY) xor
                                                 HASH('req3', S) */
                           + VC_LENGTH
                           + sizeof( crypto_provide )
                           + sizeof( padc_len );
    tr_torrent * tor = NULL;

    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    /* TODO: confirm they sent HASH('req1',S) here? */
    evbuffer_drain( inbuf, SHA_DIGEST_LENGTH );

    /* This next piece is HASH('req2', SKEY) xor HASH('req3', S) ...
     * we can get the first half of that (the obufscatedTorrentHash)
     * by building the latter and xor'ing it with what the peer sent us */
    dbgmsg( handshake, "reading obfuscated torrent hash..." );
    evbuffer_remove( inbuf, req2, SHA_DIGEST_LENGTH );
    tr_sha1( req3, "req3", 4, handshake->mySecret, KEY_LEN, NULL );
    for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
        obfuscatedTorrentHash[i] = req2[i] ^ req3[i];
    if(( tor = tr_torrentFindFromObfuscatedHash( handshake->session, obfuscatedTorrentHash )))
    {
        const bool clientIsSeed = tr_torrentIsSeed( tor );
        const bool peerIsSeed = tr_peerMgrPeerIsSeed( tor, tr_peerIoGetAddress( handshake->io, NULL ) );
        dbgmsg( handshake, "got INCOMING connection's encrypted handshake for torrent [%s]",
                tr_torrentName( tor ) );
        tr_peerIoSetTorrentHash( handshake->io, tor->info.hash );

        if( clientIsSeed && peerIsSeed )
        {
            dbgmsg( handshake, "another seed tried to reconnect to us!" );
            return tr_handshakeDone( handshake, false );
        }
    }
    else
    {
        dbgmsg( handshake, "can't find that torrent..." );
        return tr_handshakeDone( handshake, false );
    }

    /* next part: ENCRYPT(VC, crypto_provide, len(PadC), */

    tr_cryptoDecryptInit( handshake->crypto );

    tr_peerIoReadBytes( handshake->io, inbuf, vc_in, VC_LENGTH );

    tr_peerIoReadUint32( handshake->io, inbuf, &crypto_provide );
    handshake->crypto_provide = crypto_provide;
    dbgmsg( handshake, "crypto_provide is %d", (int)crypto_provide );

    tr_peerIoReadUint16( handshake->io, inbuf, &padc_len );
    dbgmsg( handshake, "padc is %d", (int)padc_len );
    handshake->pad_c_len = padc_len;
    setState( handshake, AWAITING_PAD_C );
    return READ_NOW;
}

static int
readPadC( tr_handshake *    handshake,
          struct evbuffer * inbuf )
{
    uint16_t     ia_len;
    const size_t needlen = handshake->pad_c_len + sizeof( uint16_t );

    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    evbuffer_drain( inbuf, handshake->pad_c_len );

    tr_peerIoReadUint16( handshake->io, inbuf, &ia_len );
    dbgmsg( handshake, "ia_len is %d", (int)ia_len );
    handshake->ia_len = ia_len;
    setState( handshake, AWAITING_IA );
    return READ_NOW;
}

static int
readIA( tr_handshake *    handshake,
        struct evbuffer * inbuf )
{
    const size_t      needlen = handshake->ia_len;
    struct evbuffer * outbuf;
    uint32_t          crypto_select;

    dbgmsg( handshake, "reading IA... have %zu, need %zu",
            evbuffer_get_length( inbuf ), needlen );
    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    /**
    ***  B->A: ENCRYPT(VC, crypto_select, len(padD), padD), ENCRYPT2(Payload Stream)
    **/

    tr_cryptoEncryptInit( handshake->crypto );
    outbuf = evbuffer_new( );

    dbgmsg( handshake, "sending vc" );
    /* send VC */
    {
        uint8_t vc[VC_LENGTH];
        memset( vc, 0, VC_LENGTH );
        evbuffer_add( outbuf, vc, VC_LENGTH );
    }

    /* send crypto_select */
    crypto_select = getCryptoSelect( handshake, handshake->crypto_provide );
    if( crypto_select )
    {
        dbgmsg( handshake, "selecting crypto mode '%d'", (int)crypto_select );
        evbuffer_add_uint32( outbuf, crypto_select );
    }
    else
    {
        dbgmsg( handshake, "peer didn't offer an encryption mode we like." );
        evbuffer_free( outbuf );
        return tr_handshakeDone( handshake, false );
    }

    dbgmsg( handshake, "sending pad d" );
    /* ENCRYPT(VC, crypto_provide, len(PadD), PadD
     * PadD is reserved for future extensions to the handshake...
     * standard practice at this time is for it to be zero-length */
    {
        const uint16_t len = 0;
        evbuffer_add_uint16( outbuf, len );
    }

    /* maybe de-encrypt our connection */
    if( crypto_select == CRYPTO_PROVIDE_PLAINTEXT )
    {
        tr_peerIoWriteBuf( handshake->io, outbuf, false );
        tr_peerIoSetEncryption( handshake->io, PEER_ENCRYPTION_NONE );
    }

    dbgmsg( handshake, "sending handshake" );
    /* send our handshake */
    {
        uint8_t msg[HANDSHAKE_SIZE];
        buildHandshakeMessage( handshake, msg );

        evbuffer_add( outbuf, msg, sizeof( msg ) );
        handshake->haveSentBitTorrentHandshake = 1;
    }

    /* send it out */
    tr_peerIoWriteBuf( handshake->io, outbuf, false );
    evbuffer_free( outbuf );

    /* now await the handshake */
    setState( handshake, AWAITING_PAYLOAD_STREAM );
    return READ_NOW;
}

static int
readPayloadStream( tr_handshake    * handshake,
                   struct evbuffer * inbuf )
{
    int i;
    const size_t      needlen = HANDSHAKE_SIZE;

    dbgmsg( handshake, "reading payload stream... have %zu, need %zu",
            evbuffer_get_length( inbuf ), needlen );
    if( evbuffer_get_length( inbuf ) < needlen )
        return READ_LATER;

    /* parse the handshake ... */
    i = parseHandshake( handshake, inbuf );
    dbgmsg( handshake, "parseHandshake returned %d", i );
    if( i != HANDSHAKE_OK )
        return tr_handshakeDone( handshake, false );

    /* we've completed the BT handshake... pass the work on to peer-msgs */
    return tr_handshakeDone( handshake, true );
}

/***
****
****
****
***/

static ReadState
canRead( struct tr_peerIo * io, void * arg, size_t * piece )
{
    tr_handshake    * handshake = arg;
    struct evbuffer * inbuf = tr_peerIoGetReadBuffer( io );
    ReadState         ret;
    bool              readyForMore = true;

    assert( tr_isPeerIo( io ) );

    /* no piece data in handshake */
    *piece = 0;

    dbgmsg( handshake, "handling canRead; state is [%s]",
           getStateName( handshake->state ) );

    while( readyForMore )
    {
        switch( handshake->state )
        {
            case AWAITING_HANDSHAKE:
                ret = readHandshake    ( handshake, inbuf ); break;

            case AWAITING_PEER_ID:
                ret = readPeerId       ( handshake, inbuf ); break;

            case AWAITING_YA:
                ret = readYa           ( handshake, inbuf ); break;

            case AWAITING_PAD_A:
                ret = readPadA         ( handshake, inbuf ); break;

            case AWAITING_CRYPTO_PROVIDE:
                ret = readCryptoProvide( handshake, inbuf ); break;

            case AWAITING_PAD_C:
                ret = readPadC         ( handshake, inbuf ); break;

            case AWAITING_IA:
                ret = readIA           ( handshake, inbuf ); break;

            case AWAITING_PAYLOAD_STREAM:
                ret = readPayloadStream( handshake, inbuf ); break;

            case AWAITING_YB:
                ret = readYb           ( handshake, inbuf ); break;

            case AWAITING_VC:
                ret = readVC           ( handshake, inbuf ); break;

            case AWAITING_CRYPTO_SELECT:
                ret = readCryptoSelect ( handshake, inbuf ); break;

            case AWAITING_PAD_D:
                ret = readPadD         ( handshake, inbuf ); break;

            default:
                assert( 0 );
        }

        if( ret != READ_NOW )
            readyForMore = false;
        else if( handshake->state == AWAITING_PAD_C )
            readyForMore = evbuffer_get_length( inbuf ) >= handshake->pad_c_len;
        else if( handshake->state == AWAITING_PAD_D )
            readyForMore = evbuffer_get_length( inbuf ) >= handshake->pad_d_len;
        else if( handshake->state == AWAITING_IA )
            readyForMore = evbuffer_get_length( inbuf ) >= handshake->ia_len;
    }

    return ret;
}

static bool
fireDoneFunc( tr_handshake * handshake, bool isConnected )
{
    const uint8_t * peer_id = isConnected && handshake->havePeerID
                            ? tr_peerIoGetPeersId( handshake->io )
                            : NULL;
    const bool success = ( *handshake->doneCB )( handshake,
                                                 handshake->io,
                                                 handshake->haveReadAnythingFromPeer,
                                                 isConnected,
                                                 peer_id,
                                                 handshake->doneUserData );

    return success;
}

static void
tr_handshakeFree( tr_handshake * handshake )
{
    if( handshake->io )
        tr_peerIoUnref( handshake->io ); /* balanced by the ref in tr_handshakeNew */

    event_free( handshake->timeout_timer );
    tr_free( handshake );
}

static int
tr_handshakeDone( tr_handshake * handshake, bool isOK )
{
    bool success;

    dbgmsg( handshake, "handshakeDone: %s", isOK ? "connected" : "aborting" );
    tr_peerIoSetIOFuncs( handshake->io, NULL, NULL, NULL, NULL );

    success = fireDoneFunc( handshake, isOK );

    tr_handshakeFree( handshake );

    return success ? READ_LATER : READ_ERR;
}

void
tr_handshakeAbort( tr_handshake * handshake )
{
    if( handshake != NULL )
        tr_handshakeDone( handshake, false );
}

static void
gotError( tr_peerIo  * io,
          short        what,
          void       * vhandshake )
{
    int errcode = errno;
    tr_handshake * handshake = vhandshake;

    if( io->utp_socket && !io->isIncoming && handshake->state == AWAITING_YB ) {
        /* This peer probably doesn't speak uTP. */
        tr_torrent *tor =
            tr_peerIoHasTorrentHash( io ) ?
            tr_torrentFindFromHash( handshake->session,
                                    tr_peerIoGetTorrentHash( io ) ) :
            NULL;
        /* Don't mark a peer as non-uTP unless it's really a connect failure. */
        if( tor && ( errcode == ETIMEDOUT || errcode == ECONNREFUSED ) ) {
            tr_torrentLock( tor );
            tr_peerMgrSetUtpFailed( tor,
                                    tr_peerIoGetAddress( io, NULL ),
                                    true );
            tr_torrentUnlock( tor );
        }

        if( !tr_peerIoReconnect( handshake->io ) ) {
            uint8_t msg[HANDSHAKE_SIZE];
            buildHandshakeMessage( handshake, msg );
            handshake->haveSentBitTorrentHandshake = 1;
            setReadState( handshake, AWAITING_HANDSHAKE );
            tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), false );
        }
    }

    /* if the error happened while we were sending a public key, we might
     * have encountered a peer that doesn't do encryption... reconnect and
     * try a plaintext handshake */
    if( ( ( handshake->state == AWAITING_YB )
        || ( handshake->state == AWAITING_VC ) )
      && ( handshake->encryptionMode != TR_ENCRYPTION_REQUIRED )
      && ( !tr_peerIoReconnect( handshake->io ) ) )
    {
        uint8_t msg[HANDSHAKE_SIZE];

        dbgmsg( handshake, "handshake failed, trying plaintext..." );
        buildHandshakeMessage( handshake, msg );
        handshake->haveSentBitTorrentHandshake = 1;
        setReadState( handshake, AWAITING_HANDSHAKE );
        tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), false );
    }
    else
    {
        dbgmsg( handshake, "libevent got an error what==%d, errno=%d (%s)",
               (int)what, errno, tr_strerror( errno ) );
        tr_handshakeDone( handshake, false );
    }
}

/**
***
**/

static void
handshakeTimeout( int foo UNUSED, short bar UNUSED, void * handshake )
{
    tr_handshakeAbort( handshake );
}

tr_handshake*
tr_handshakeNew( tr_peerIo           * io,
                 tr_encryption_mode    encryptionMode,
                 handshakeDoneCB       doneCB,
                 void                * doneUserData )
{
    tr_handshake * handshake;
    tr_session * session = tr_peerIoGetSession( io );

    handshake = tr_new0( tr_handshake, 1 );
    handshake->io = io;
    handshake->crypto = tr_peerIoGetCrypto( io );
    handshake->encryptionMode = encryptionMode;
    handshake->doneCB = doneCB;
    handshake->doneUserData = doneUserData;
    handshake->session = session;
    handshake->timeout_timer = evtimer_new( session->event_base, handshakeTimeout, handshake );
    tr_timerAdd( handshake->timeout_timer, HANDSHAKE_TIMEOUT_SEC, 0 );

    tr_peerIoRef( io ); /* balanced by the unref in tr_handshakeFree */
    tr_peerIoSetIOFuncs( handshake->io, canRead, NULL, gotError, handshake );
    tr_peerIoSetEncryption( io, PEER_ENCRYPTION_NONE );

    if( tr_peerIoIsIncoming( handshake->io ) )
        setReadState( handshake, AWAITING_HANDSHAKE );
    else if( encryptionMode != TR_CLEAR_PREFERRED )
        sendYa( handshake );
    else
    {
        uint8_t msg[HANDSHAKE_SIZE];
        buildHandshakeMessage( handshake, msg );

        handshake->haveSentBitTorrentHandshake = 1;
        setReadState( handshake, AWAITING_HANDSHAKE );
        tr_peerIoWriteBytes( handshake->io, msg, sizeof( msg ), false );
    }

    return handshake;
}

struct tr_peerIo*
tr_handshakeGetIO( tr_handshake * handshake )
{
    assert( handshake );
    assert( handshake->io );

    return handshake->io;
}

struct tr_peerIo*
tr_handshakeStealIO( tr_handshake * handshake )
{
    struct tr_peerIo * io;

    assert( handshake );
    assert( handshake->io );

    io = handshake->io;
    handshake->io = NULL;
    return io;
}

const tr_address *
tr_handshakeGetAddr( const struct tr_handshake * handshake,
                     tr_port                   * port )
{
    assert( handshake );
    assert( handshake->io );

    return tr_peerIoGetAddress( handshake->io, port );
}

