/******************************************************************************
 * $Id: client.c 3578 2007-10-26 03:43:27Z joshe $
 *
 * Copyright (c) 2007 Joshua Elsasser
 *
 * 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.
 *****************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <event.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <libtransmission/bsdtree.h>
#include <libtransmission/bencode.h>
#include <libtransmission/ipcparse.h>
#include <libtransmission/trcompat.h>

#include "bsdqueue.h"
#include "client.h"
#include "errors.h"
#include "misc.h"

/* time out server after this many seconds */
#define SERVER_TIMEOUT          ( 15 )

struct con
{
    int                  infd;
    int                  outfd;
    struct ipc_info    * ipc;
    struct bufferevent * evin;
    struct bufferevent * evout;
};

struct req
{
    enum ipc_msg       id;
    int64_t            tag;
    struct strlist   * strs;
    int64_t            num;
    char             * str;
    size_t             listlen;
    int64_t          * numlist;
    uint8_t          * buf;
    int                types;
    SLIST_ENTRY( req ) next;
};

SLIST_HEAD( reqlist, req );

struct resp
{
    int64_t            tag;
    cl_infofunc        infocb;
    cl_statfunc        statcb;
    RB_ENTRY( resp )   links;
};

RB_HEAD( resptree, resp );

static struct req * addreq   ( enum ipc_msg, int64_t, struct resp ** );
static int     addintlistreq ( enum ipc_msg, size_t, const int * );
static void    noop          ( struct bufferevent *, void * );
static void    noway         ( struct bufferevent *, void * );
static void    didwrite      ( struct bufferevent *, void * );
static void    ohshit        ( struct bufferevent *, short, void * );
static void    canread       ( struct bufferevent *, void * );
static void    flushreqs     ( struct con * );
static int     sendvers      ( struct con * );
static void    infomsg       ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void    statmsg       ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void    defmsg        ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void    cbdone        ( struct resp * );
static int64_t getinfoint    ( enum ipc_msg, benc_val_t *, int, int64_t );
static char *  getinfostr    ( enum ipc_msg, benc_val_t *, int, char * );
static int     resptagcmp    ( struct resp *, struct resp * );

RB_GENERATE_STATIC( resptree, resp, links, resptagcmp )
INTCMP_FUNC( resptagcmp, resp, tag )

static struct event_base * gl_base   = NULL;
static struct ipc_funcs  * gl_tree   = NULL;
static struct reqlist      gl_reqs   = SLIST_HEAD_INITIALIZER( &gl_reqs );
static struct resptree     gl_resps  = RB_INITIALIZER( &gl_resps );
static int64_t             gl_tag    = 0;
static int                 gl_proxy  = -1;

int
client_init( struct event_base * base )
{
    assert( NULL == gl_base && NULL == gl_tree );
    gl_base   = base;
    gl_tree   = ipc_initmsgs();
    if( NULL == gl_tree )
    {
        return -1;
    }

    if( 0 > ipc_addmsg( gl_tree, IPC_MSG_INFO, infomsg ) ||
        0 > ipc_addmsg( gl_tree, IPC_MSG_STAT, statmsg ) )
    {
        return -1;
    }

    ipc_setdefmsg( gl_tree, defmsg );

    return 0;
}

int
client_new_sock( const char * path )
{
    struct sockaddr_un sa;
    int                fd;
    struct con       * con;

    assert( NULL != gl_base );
    assert( 0 > gl_proxy );
    assert( NULL != path );

    gl_proxy = 0;

    memset( &sa, 0, sizeof sa );
    sa.sun_family = AF_LOCAL;
    strlcpy( sa.sun_path, path, sizeof sa.sun_path );

    fd = socket( AF_UNIX, SOCK_STREAM, 0 );
    if( 0 > fd )
    {
        errnomsg( "failed to create socket" );
        return -1;
    }

    if( 0 > connect( fd, ( struct sockaddr * )&sa, SUN_LEN( &sa ) ) )
    {
        errnomsg( "failed to connect to socket file: %s", path );
        close( fd );
        return -1;
    }
    con = calloc( 1, sizeof *con );
    if( NULL == con )
    {
        mallocmsg( sizeof *con );
        close( fd );
        return -1;
    }
    con->ipc = ipc_newcon( gl_tree );
    if( NULL == con->ipc )
    {
        mallocmsg( sizeof *con->ipc );
        close( fd );
        free( con );
    }
    con->infd = fd;
    con->evin = bufferevent_new( fd, canread, didwrite, ohshit, con );
    if( NULL == con->evin )
    {
        errnomsg( "failed to create bufferevent" );
        close( fd );
        ipc_freecon( con->ipc );
        free( con );
        return -1;
    }
    con->outfd = con->infd;
    con->evout = con->evin;
    /* XXX bufferevent_base_set( gl_base, con->evin ); */
    bufferevent_settimeout( con->evin, SERVER_TIMEOUT, SERVER_TIMEOUT );
    bufferevent_enable( con->evin, EV_READ );
    if( 0 > sendvers( con ) )
    {
        exit( 1 );
    }

    return 0;
}

int
client_new_cmd( char * const * cmd )
{
    struct con * con;
    int          tocmd[2], fromcmd[2];
    pid_t        kid;

    assert( NULL != gl_base );
    assert( 0 > gl_proxy );
    assert( NULL != cmd && NULL != cmd[0] );

    gl_proxy = 1;

    if( 0 > pipe( tocmd ) )
    {
        errnomsg( "failed to create pipe" );
        return -1;
    }

    if( 0 > pipe( fromcmd ) )
    {
        errnomsg( "failed to create pipe" );
        close( tocmd[0] );
        close( tocmd[1] );
        return -1;
    }

    kid = fork();
    if( 0 > kid )
    {
        close( tocmd[0] );
        close( tocmd[1] );
        close( fromcmd[0] );
        close( fromcmd[1] );
        return -1;
    }
    else if( 0 == kid )
    {
        if( 0 > dup2( tocmd[0], STDIN_FILENO ) ||
            0 > dup2( fromcmd[1], STDOUT_FILENO ) )
        {
            errnomsg( "failed to duplicate descriptors" );
            _exit( 1 );
        }
        close( tocmd[0] );
        close( tocmd[1] );
        close( fromcmd[0] );
        close( fromcmd[1] );
        execvp( cmd[0], cmd );
        errnomsg( "failed to execute: %s", cmd[0] );
        _exit( 1 );
    }

    close( tocmd[0] );
    close( fromcmd[1] );

    con = calloc( 1, sizeof *con );
    if( NULL == con )
    {
        mallocmsg( sizeof *con );
        close( tocmd[1] );
        close( fromcmd[0] );
        return -1;
    }

    con->infd = fromcmd[0];
    con->evin = bufferevent_new( con->infd, canread, noop, ohshit, con );
    if( NULL == con->evin )
    {
        free( con );
        close( tocmd[1] );
        close( fromcmd[0] );
        return -1;
    }
    /* XXX bufferevent_base_set( gl_base, con->evin ); */
    bufferevent_settimeout( con->evin, SERVER_TIMEOUT, SERVER_TIMEOUT );
    bufferevent_enable( con->evin, EV_READ );

    con->outfd = tocmd[1];
    con->evout = bufferevent_new( con->outfd, noway, didwrite, ohshit, con );
    if( NULL == con->evout )
    {
        bufferevent_free( con->evin );
        bufferevent_free( con->evout );
        free( con );
        close( tocmd[1] );
        close( fromcmd[0] );
        return -1;
    }
    /* XXX bufferevent_base_set( gl_base, con->evout ); */
    bufferevent_settimeout( con->evout, SERVER_TIMEOUT, SERVER_TIMEOUT );
    bufferevent_enable( con->evout, EV_READ );

    con->ipc = ipc_newcon( gl_tree );
    if( NULL == con->ipc )
    {
        mallocmsg( sizeof *con->ipc );
        bufferevent_free( con->evin );
        bufferevent_free( con->evout );
        free( con );
        close( tocmd[1] );
        close( fromcmd[0] );
        return -1;
    }

    if( 0 > sendvers( con ) )
    {
        exit( 1 );
    }

    return 0;
}

struct req *
addreq( enum ipc_msg id, int64_t tag, struct resp ** resp )
{
    struct req * req;

    assert( ( 0 < tag && NULL != resp ) || ( 0 >= tag && NULL == resp ) );

    req = calloc( 1, sizeof *req );
    if( NULL == req )
    {
        mallocmsg( sizeof *req );
        return NULL;
    }

    if( NULL != resp )
    {
        *resp = calloc( 1, sizeof **resp );
        if( NULL == *resp )
        {
            mallocmsg( sizeof **resp );
            free( req );
            return NULL;
        }
        (*resp)->tag = tag;
        RB_INSERT( resptree, &gl_resps, *resp );
    }

    req->id  = id;
    req->tag = tag;
    SLIST_INSERT_HEAD( &gl_reqs, req, next );

    return req;
}

int
client_quit( void )
{
    return ( NULL == addreq( IPC_MSG_QUIT, -1, NULL ) ? -1 : 0 );
}

int
client_addfiles( struct strlist * list )
{
    struct stritem * ii;
    uint8_t        * buf;
    size_t           size;
    struct req     * req;

    if( gl_proxy )
    {
        SLIST_FOREACH( ii, list, next )
        {
            buf = readfile( ii->str, &size );
            req = addreq( IPC_MSG_ADDONEFILE, -1, NULL );
            if( NULL == req )
            {
                free( buf );
                return -1;
            }
            req->buf     = buf;
            req->listlen = size;
        }
    }
    else
    {
        req = addreq( IPC_MSG_ADDMANYFILES, -1, NULL );
        if( NULL == req )
        {
            return -1;
        }

        /* XXX need to move arg parsing back here or something */
        req->strs = list;
    }

    return 0;
}

int
client_automap( int automap )
{
    struct req * req;

    req = addreq( IPC_MSG_AUTOMAP, -1, NULL );
    if( NULL == req )
    {
        return -1;
    }

    req->num = ( automap ? 1 : 0 );

    return 0;
}

int
client_pex( int pex )
{
    struct req * req;

    req = addreq( IPC_MSG_PEX, -1, NULL );
    if( NULL == req )
    {
        return -1;
    }

    req->num = ( pex ? 1 : 0 );

    return 0;
}

int
client_port( int port )
{
    struct req * req;

    req = addreq( IPC_MSG_PORT, -1, NULL );
    if( NULL == req )
    {
        return -1;
    }

    req->num = port;

    return 0;
}

int
client_downlimit( int limit )
{
    struct req * req;

    req = addreq( IPC_MSG_DOWNLIMIT, -1, NULL );
    if( NULL == req )
    {
        return -1;
    }

    req->num = ( 0 > limit ? -1 : limit );

    return 0;
}

int
client_uplimit( int limit )
{
    struct req * req;

    req = addreq( IPC_MSG_UPLIMIT, -1, NULL );
    if( NULL == req )
    {
        return -1;
    }

    req->num = ( 0 > limit ? -1 : limit );

    return 0;
}

int
client_dir( const char * dir )
{
    struct req * req;
    char       * dircpy;

    dircpy = strdup( dir );
    if( NULL == dircpy )
    {
        mallocmsg( strlen( dir ) );
        return -1;
    }

    req = addreq( IPC_MSG_DIR, -1, NULL );
    if( NULL == req )
    {
        free( dircpy );
        return -1;
    }

    req->str = dircpy;

    return 0;
}

int
client_crypto( const char * mode )
{
    struct req * req;
    char       * modecpy;

    modecpy = strdup( mode );
    if( NULL == modecpy )
    {
        mallocmsg( strlen( mode ) );
        return -1;
    }

    req = addreq( IPC_MSG_CRYPTO, -1, NULL );
    if( NULL == req )
    {
        free( modecpy );
        return -1;
    }

    req->str = modecpy;

    return 0;
}

int
addintlistreq( enum ipc_msg which, size_t len, const int * list )
{
    struct req * req;
    int64_t    * duplist;
    size_t       ii;

    assert( ( 0 == len && NULL == list ) || ( 0 < len && NULL != list ) );

    duplist = NULL;
    if( NULL != list )
    {
        duplist = calloc( len, sizeof duplist[0] );
        if( NULL == duplist )
        {
            mallocmsg( len * sizeof( duplist[0] ) );
            return -1;
        }
    }

    req = addreq( which, -1, NULL );
    if( NULL == req )
    {
        free( duplist );
        return -1;
    }

    for( ii = 0; len > ii; ii++ )
    {
        duplist[ii] = list[ii];
    }
    req->listlen = len;
    req->numlist = duplist;

    return 0;
}

int
client_start( size_t len, const int * list )
{
    enum ipc_msg id;

    id = ( NULL == list ? IPC_MSG_STARTALL : IPC_MSG_START );

    return addintlistreq( id, len, list );
}

int
client_stop( size_t len, const int * list )
{
    enum ipc_msg id;

    id = ( NULL == list ? IPC_MSG_STOPALL : IPC_MSG_STOP );

    return addintlistreq( id, len, list );
}

int
client_remove( size_t len, const int * list )
{
    enum ipc_msg id;

    id = ( NULL == list ? IPC_MSG_REMOVEALL : IPC_MSG_REMOVE );

    return addintlistreq( id, len, list );
}

int
client_list( cl_infofunc func )
{
    struct req  * req;
    struct resp * resp;

    req = addreq( IPC_MSG_GETINFOALL, ++gl_tag, &resp );
    if( NULL == req )
    {
        return -1;
    }

    resp->infocb = func;
    req->types   = IPC_INF_NAME | IPC_INF_HASH;

    return 0;
}

int
client_info( cl_infofunc func )
{
    struct req  * req;
    struct resp * resp;

    req = addreq( IPC_MSG_GETINFOALL, ++gl_tag, &resp );
    if( NULL == req )
    {
        return -1;
    }

    resp->infocb = func;
    req->types   = IPC_INF_NAME | IPC_INF_HASH | IPC_INF_SIZE;

    return 0;
}

int
client_hashids( cl_infofunc func )
{
    struct req  * req;
    struct resp * resp;

    req = addreq( IPC_MSG_GETINFOALL, ++gl_tag, &resp );
    if( NULL == req )
    {
        return -1;
    }

    resp->infocb = func;
    req->types   = IPC_INF_HASH;

    return 0;
}

int
client_status( cl_statfunc func )
{
    struct req  * req;
    struct resp * resp;

    req = addreq( IPC_MSG_GETSTATALL, ++gl_tag, &resp );
    if( NULL == req )
    {
        return -1;
    }

    resp->statcb = func;
    req->types   = IPC_ST_STATE | IPC_ST_ETA | IPC_ST_COMPLETED |
        IPC_ST_DOWNSPEED | IPC_ST_UPSPEED | IPC_ST_DOWNTOTAL | IPC_ST_UPTOTAL |
        IPC_ST_ERROR | IPC_ST_ERRMSG;

    return 0;
}

void
noop( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
    /* libevent prior to 1.2 couldn't handle a NULL write callback */
}

void
noway( struct bufferevent * evin, void * arg UNUSED )
{
    /* this shouldn't happen, but let's drain the buffer anyway */
    evbuffer_drain( EVBUFFER_INPUT( evin ),
                    EVBUFFER_LENGTH( EVBUFFER_INPUT( evin ) ) );
}

void
didwrite( struct bufferevent * evout, void * arg )
{
    struct con * con = arg;

    assert( evout == con->evout );
    flushreqs( con );
}

void
ohshit( struct bufferevent * ev UNUSED, short what, void * arg UNUSED )
{
    if( EVBUFFER_EOF & what )
    {
        errmsg( "server closed connection" );
    }
    else if( EVBUFFER_TIMEOUT & what )
    {
        errmsg( "server connection timed out" );
    }
    else if( EVBUFFER_READ & what )
    {
        errmsg( "read error on server connection" );
    }
    else if( EVBUFFER_WRITE & what )
    {
        errmsg( "write error on server connection" );
    }
    else if( EVBUFFER_ERROR & what )
    {
        errmsg( "error on server connection" );
    }
    else
    {
        errmsg( "unknown error on server connection: 0x%x", what );
    }
    exit( 1 );
}

void
canread( struct bufferevent * evin, void * arg )
{
    struct con * con = arg;
    uint8_t    * buf;
    size_t       len;
    ssize_t      res;

    assert( evin == con->evin );
    buf = EVBUFFER_DATA( EVBUFFER_INPUT( evin ) );
    len = EVBUFFER_LENGTH( EVBUFFER_INPUT( evin ) );

    if( IPC_MIN_MSG_LEN > len )
    {
        return;
    }

    res = ipc_parse( con->ipc, buf, len, con );
    if( 0 > res )
    {
        switch( errno )
        {
            case EPERM:
                errmsg( "unsupported protocol version" );
                break;
            case EINVAL:
                errmsg( "protocol parse error" );
                break;
            default:
                errnomsg( "parsing failed" );
                break;
        }
        exit( 1 );
    }

    if( 0 < res )
    {
        evbuffer_drain( EVBUFFER_INPUT( evin ), res );
        flushreqs( con );
    }
}

void
flushreqs( struct con * con )
{
    struct req     * req;
    uint8_t        * buf;
    size_t           buflen, ii;
    benc_val_t       pk, * val;
    struct stritem * jj;

    if( !HASVERS( con->ipc ) )
    {
        return;
    }

    if( SLIST_EMPTY( &gl_reqs ) && RB_EMPTY( &gl_resps ) )
    {
        exit( 0 );
    }

    while( !SLIST_EMPTY( &gl_reqs ) )
    {
        req = SLIST_FIRST( &gl_reqs );
        SLIST_REMOVE_HEAD( &gl_reqs, next );
        buf = NULL;
        switch( req->id )
        {
            case IPC_MSG_QUIT:
            case IPC_MSG_STARTALL:
            case IPC_MSG_STOPALL:
            case IPC_MSG_REMOVEALL:
                buf = ipc_mkempty( con->ipc, &buflen, req->id, req->tag );
                break;
            case IPC_MSG_ADDMANYFILES:
                ii = 0;
                SLIST_FOREACH( jj, req->strs, next )
                {
                    ii++;
                }
                val = ipc_initval( con->ipc, req->id, -1, &pk, TYPE_LIST );
                if( NULL != val && !tr_bencListReserve( val, ii ) )
                {
                    SLIST_FOREACH( jj, req->strs, next )
                    {
                        tr_bencInitStr( tr_bencListAdd( val ),
                                        jj->str, -1, 1 );
                    }
                    buf = ipc_mkval( &pk, &buflen );
                    SAFEBENCFREE( &pk );
                }
                SAFEFREESTRLIST( req->strs );
                break;
            case IPC_MSG_ADDONEFILE:
                val = ipc_initval( con->ipc, req->id, -1, &pk, TYPE_DICT );
                if( NULL != val && !tr_bencDictReserve( val, 1 ) )
                {
                    tr_bencInitStr( tr_bencDictAdd( val, "data" ),
                                    req->buf, req->listlen, 1 );
                    buf = ipc_mkval( &pk, &buflen );
                    SAFEBENCFREE( &pk );
                }
                SAFEFREE( req->buf );
                break;
            case IPC_MSG_AUTOMAP:
            case IPC_MSG_PORT:
            case IPC_MSG_DOWNLIMIT:
            case IPC_MSG_UPLIMIT:
            case IPC_MSG_PEX:
                buf = ipc_mkint( con->ipc, &buflen, req->id, -1, req->num );
                break;
            case IPC_MSG_CRYPTO:
            case IPC_MSG_DIR:
                buf = ipc_mkstr( con->ipc, &buflen, req->id, -1, req->str );
                SAFEFREE( req->str );
                break;
            case IPC_MSG_START:
            case IPC_MSG_STOP:
            case IPC_MSG_REMOVE:
                val = ipc_initval( con->ipc, req->id, -1, &pk, TYPE_LIST );
                if( NULL != val && !tr_bencListReserve( val, req->listlen ) )
                {
                    for( ii = 0; ii < req->listlen; ii++ )
                    {
                        tr_bencInitInt( tr_bencListAdd( val ),
                                        req->numlist[ii] );
                    }
                    buf = ipc_mkval( &pk, &buflen );
                    SAFEBENCFREE( &pk );
                }
                SAFEFREE( req->numlist );
                break;
            case IPC_MSG_GETINFOALL:
            case IPC_MSG_GETSTATALL:
                buf = ipc_mkgetinfo( con->ipc, &buflen, req->id, req->tag,
                                     req->types, NULL );
                break;
            default:
                assert( 0 );
                return;
        }

        SAFEFREE( req );
        if( NULL == buf )
        {
            if( EPERM == errno )
            {
                errmsg( "message not supported by server" );
            }
            else
            {
                errnomsg( "failed to create message" );
            }
            exit( 1 );
        }
        if( 0 > bufferevent_write( con->evout, buf, buflen ) )
        {
            errmsg( "failed to buffer %zd bytes of data for write", buflen );
            exit( 1 );
        }
        free( buf );
    }
}

int
sendvers( struct con * con )
{
    uint8_t   * buf;
    size_t      len;

    buf = ipc_mkvers( &len, "Transmission remote" LONG_VERSION_STRING );
    if( NULL == buf )
    {
        if( EPERM == errno )
        {
            errmsg( "message not supported by server" );
        }
        else
        {
            errnomsg( "failed to create message" );
        }
        return -1;
    }

    if( 0 > bufferevent_write( con->evout, buf, len ) )
    {
        free( buf );
        errmsg( "failed to buffer %i bytes of data for write", ( int )len );
        return -1;
    }

    free( buf );

    return 0;
}

void
infomsg( enum ipc_msg msgid, benc_val_t * list, int64_t tag,
         void * arg UNUSED )
{
    benc_val_t   * dict;
    int            ii;
    struct cl_info inf;
    int64_t        id;
    struct resp  * resp, key;

    assert( IPC_MSG_INFO == msgid );

    if( TYPE_LIST != list->type )
    {
        return;
    }

    memset( &key, 0, sizeof key );
    key.tag = tag;
    resp = RB_FIND( resptree, &gl_resps, &key );
    if( NULL == resp || NULL == resp->infocb )
    {
        return;
    }
    RB_REMOVE( resptree, &gl_resps, resp );

    for( ii = 0; list->val.l.count > ii; ii++ )
    {
        dict = &list->val.l.vals[ii];
        if( TYPE_DICT != dict->type )
        {
            continue;
        }

        id       = getinfoint( msgid, dict, IPC_INF_ID,   -1   );
        inf.name = getinfostr( msgid, dict, IPC_INF_NAME, NULL );
        inf.hash = getinfostr( msgid, dict, IPC_INF_HASH, NULL );
        inf.size = getinfoint( msgid, dict, IPC_INF_SIZE, -1   );

        if( !TORRENT_ID_VALID( id ) )
        {
            continue;
        }

        inf.id = id;
        resp->infocb( &inf );
    }

    cbdone( resp );
    free( resp );
}

void
statmsg( enum ipc_msg msgid, benc_val_t * list, int64_t tag,
         void * arg UNUSED )
{
    benc_val_t   * dict;
    int            ii;
    int64_t        id;
    struct cl_stat st;
    struct resp  * resp, key;

    assert( IPC_MSG_STAT == msgid );

    if( TYPE_LIST != list->type )
    {
        return;
    }

    memset( &key, 0, sizeof key );
    key.tag = tag;
    resp = RB_FIND( resptree, &gl_resps, &key );
    if( NULL == resp || NULL == resp->statcb )
    {
        return;
    }
    RB_REMOVE( resptree, &gl_resps, resp );

    for( ii = 0; list->val.l.count > ii; ii++ )
    {
        dict   = &list->val.l.vals[ii];
        if( TYPE_DICT != dict->type )
        {
            continue;
        }

        id           = getinfoint( msgid, dict, IPC_ST_ID,        -1   );
        st.state     = getinfostr( msgid, dict, IPC_ST_STATE,     NULL );
        st.eta       = getinfoint( msgid, dict, IPC_ST_ETA,       -1   );
        st.done      = getinfoint( msgid, dict, IPC_ST_COMPLETED, -1   );
        st.ratedown  = getinfoint( msgid, dict, IPC_ST_DOWNSPEED, -1   );
        st.rateup    = getinfoint( msgid, dict, IPC_ST_UPSPEED,   -1   );
        st.totaldown = getinfoint( msgid, dict, IPC_ST_DOWNTOTAL, -1   );
        st.totalup   = getinfoint( msgid, dict, IPC_ST_UPTOTAL,   -1   );
        st.error     = getinfostr( msgid, dict, IPC_ST_ERROR,     NULL );
        st.errmsg    = getinfostr( msgid, dict, IPC_ST_ERRMSG,    NULL );

        if( !TORRENT_ID_VALID( id ) )
        {
            continue;
        }

        st.id = id;
        resp->statcb( &st );
    }

    cbdone( resp );
    free( resp );
}

void
defmsg( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg UNUSED )
{
    struct resp * resp, key;

    switch( msgid )
    {
        case IPC_MSG_FAIL:
            if( TYPE_STR == val->type && NULL != val->val.s.s )
            {
                errmsg( "request failed: %s", val->val.s.s );
            }
            else
            {
                errmsg( "request failed" );
            }
            break;
        case IPC_MSG_NOTSUP:
            errmsg( "request message not supported" );
            break;
        default:
            break;
    }

    memset( &key, 0, sizeof key );
    key.tag = tag;
    resp = RB_FIND( resptree, &gl_resps, &key );
    if( NULL == resp )
    {
        return;
    }
    RB_REMOVE( resptree, &gl_resps, resp );

    cbdone( resp );
    free( resp );
}

void
cbdone( struct resp * resp )
{
    if( NULL != resp->infocb )
    {
        resp->infocb( NULL );
    }
    else if( NULL != resp->statcb )
    {
        resp->statcb( NULL );
    }
}

int64_t
getinfoint( enum ipc_msg msgid, benc_val_t * dict, int type, int64_t defval  )
{
    benc_val_t * val;

    val = tr_bencDictFind( dict, ipc_infoname( msgid, type ) );

    if( NULL != val && TYPE_INT == val->type )
    {
        return val->val.i;
    }

    return defval;
}

char *
getinfostr( enum ipc_msg msgid, benc_val_t * dict, int type, char * defval  )
{
    benc_val_t * val;

    val = tr_bencDictFind( dict, ipc_infoname( msgid, type ) );

    if( NULL != val && TYPE_STR == val->type )
    {
        return val->val.s.s ;
    }

    return defval;
}
