/******************************************************************************
 * $Id: IPCController.m 4514 2008-01-06 07:02:57Z charles $
 *
 * Copyright (c) 2007-2008 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.
 *****************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#include "bencode.h"
#include "ipcparse.h"
#include "transmission.h"

#import "IPCController.h"
#import "Torrent.h"
#import "PrefsController.h"

static void
getaddr( struct sockaddr_un * );
static NSArray *
bencarray( benc_val_t * val, int type );
static void
msg_lookup   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
static void
msg_info     ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_infoall  ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_action   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_actionall( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_addold   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_addnew   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_getbool  ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_getint   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_getstr   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_setbool  ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_setint   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_setstr   ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_empty    ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
void
msg_sup      ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );
static void
msg_default  ( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg );

@interface IPCClient : NSObject
{
    NSFileHandle    * _handle;
    struct ipc_info * _ipc;
    IPCController   * _controller;
    NSMutableData   * _buf;
}

- (id)              initClient: (IPCController *) controller
                         funcs: (struct ipc_funcs *) funcs
                        handle: (NSFileHandle *) handle;
- (IPCController *) controller;
- (struct ipc_info *)      ipc;
- (void)               gotdata: (NSNotification *) notification;
- (BOOL)              sendresp: (uint8_t *) buf
                          size: (size_t) size;
- (BOOL)         sendrespEmpty: (enum ipc_msg) msgid
                           tag: (int64_t) tag;
- (BOOL)           sendrespInt: (enum ipc_msg) msgid
                           tag: (int64_t) tag
                           val: (int64_t) val;
- (BOOL)           sendrespStr: (enum ipc_msg) msgid
                           tag: (int64_t) tag
                           val: (NSString *) val;
- (void)          sendrespInfo: (enum ipc_msg) respid
                           tag: (int64_t) tag
                      torrents: (NSArray *) tors
                         types: (int) types;

@end

@interface IPCController (Private)

- (void)  newclient: (NSNotification *) notification;
- (void) killclient: (IPCClient *) client;

NSUserDefaults          * fDefaults;
PrefsController         * fPrefsController;


@end

@implementation IPCController

- (id) init
{
    struct sockaddr_un sun;

    self = [super init];
    if( nil == self )
        return nil;

    getaddr( &sun );
    unlink( sun.sun_path );
    _sock    = [[NSSocketPort alloc]
                   initWithProtocolFamily: PF_UNIX
                               socketType: SOCK_STREAM
                                 protocol: 0
                                  address: [NSData dataWithBytes: &sun
                                                          length: sizeof(sun)]];
    _listen  = [[NSFileHandle alloc]
                   initWithFileDescriptor: [_sock socket]
                           closeOnDealloc: YES];
    _funcs   = ipc_initmsgs();
    _clients = [[NSMutableArray alloc] init];

    /* XXX is this error checking bogus? */
    if( nil  == _sock    ||
        nil  == _listen  ||
        NULL == _funcs   ||
        nil  == _clients ||
        0 > ipc_addmsg( _funcs, IPC_MSG_ADDMANYFILES, msg_addold    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_ADDONEFILE,   msg_addnew    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_AUTOMAP,      msg_setbool   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_AUTOSTART,    msg_setbool   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_CRYPTO,       msg_setstr    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_DIR,          msg_setstr    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_DOWNLIMIT,    msg_setint    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETAUTOMAP,   msg_getbool   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETAUTOSTART, msg_getbool   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETCRYPTO,    msg_getstr    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETDIR,       msg_getstr    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETDOWNLIMIT, msg_getint    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETINFO,      msg_info      ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETINFOALL,   msg_infoall   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETPEX,       msg_getbool   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETPORT,      msg_getint    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETSTAT,      msg_info      ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETSTATALL,   msg_infoall   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_GETUPLIMIT,   msg_getint    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_LOOKUP,       msg_lookup    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_NOOP,         msg_empty     ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_PEX,          msg_setbool   ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_PORT,         msg_setint    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_QUIT,         msg_empty     ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_REMOVE,       msg_action    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_REMOVEALL,    msg_actionall ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_START,        msg_action    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_STARTALL,     msg_actionall ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_STOP,         msg_action    ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_STOPALL,      msg_actionall ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_SUP,          msg_sup       ) ||
        0 > ipc_addmsg( _funcs, IPC_MSG_UPLIMIT,      msg_setint    ) )
    {
        [self release];
        return nil;
    }
    ipc_setdefmsg( _funcs, msg_default );

    [[NSNotificationCenter defaultCenter]
        addObserver: self
           selector: @selector(newclient:)
               name: NSFileHandleConnectionAcceptedNotification
             object: _listen];
    [_listen acceptConnectionInBackgroundAndNotify];

    return self;
}

- (void) dealloc
{
    struct sockaddr_un sun;

    [[NSNotificationCenter defaultCenter] removeObserver: self];
    [_listen  release];
    [_sock    release];
    [_clients release];
    ipc_freemsgs( _funcs );
    getaddr( &sun );
    unlink( sun.sun_path );
    [super dealloc];
}

- (id) delegate
{
    return _delegate;
}

- (void) setDelegate: (id) newdelegate
{
    _delegate = newdelegate;
}

- (void) setPrefsController: (id) thePrefsController
{
    fPrefsController = thePrefsController;
}

@end

@implementation IPCController (Private)

- (void) newclient: (NSNotification *) notification
{
    NSDictionary * info;
    NSFileHandle * handle;
    NSNumber     * error;
    IPCClient    * client;
    

    info   = [notification userInfo];
    handle = [info objectForKey: NSFileHandleNotificationFileHandleItem];
    error  = [info objectForKey: @"NSFileHandleError"];

    if( nil != error )
    {
        NSLog( @"Failed to accept IPC socket connection: %@", error );
        return;
    }

    [_listen acceptConnectionInBackgroundAndNotify];

    if( nil == handle )
        return;

    client = [[IPCClient alloc]
                 initClient: self
                      funcs: _funcs
                     handle: handle];
    if( nil == client )
        return;

    [_clients addObject:client];
    [client release];
}

- (void) killclient: (IPCClient *) client
{
    [_clients removeObject: client];
}

@end

@implementation IPCClient

- (id) initClient: (IPCController *) controller
            funcs: (struct ipc_funcs *) funcs
           handle: (NSFileHandle *) handle
{
    uint8_t * buf;
    size_t    size;

    self = [super init];
    if( nil == self )
        return nil;

    _handle     = [handle retain];
    _ipc        = ipc_newcon( funcs );
    _controller = controller;
    _buf        = [[NSMutableData alloc] init];

    buf = ipc_mkvers( &size, "Transmission Mac OS X " LONG_VERSION_STRING );
    if( NULL == _ipc || nil == _buf || NULL == buf ||
        ![self sendresp: buf size: size] )
    {
        [self release];
        return nil;
    }

    [[NSNotificationCenter defaultCenter]
        addObserver: self
           selector: @selector(gotdata:)
               name: NSFileHandleReadCompletionNotification
             object: _handle];
    [_handle readInBackgroundAndNotify];

    return self;
}

- (void) dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver: self];
    [_handle release];
    [_buf    release];
    ipc_freecon( _ipc );
    [super   dealloc];
}

- (IPCController *) controller
{
    return _controller;
}

- (struct ipc_info *) ipc
{
    return _ipc;
}

- (void) gotdata: (NSNotification *) notification
{
    NSDictionary * info;
    NSData       * data;
    NSNumber     * error;
    ssize_t        res;

    info  = [notification userInfo];
    data  = [info objectForKey: NSFileHandleNotificationDataItem];
    error = [info objectForKey: @"NSFileHandleError"];

    if( nil != error )
    {
        NSLog( @"Failed to read from IPC socket connection: %@", error );
        [_controller killclient: self];
        return;
    }

    if( nil == data || 0 == [data length] )
    {
        [_controller killclient: self];
        return;
    }

    [_handle readInBackgroundAndNotify];
    [_buf appendData: data];

    if( IPC_MIN_MSG_LEN > [_buf length] )
        return;

    res = ipc_parse( _ipc, [_buf mutableBytes], [_buf length], self );

    if( 0 > res )
    {
        switch( errno )
        {
            case EPERM:
                NSLog( @"IPC client has unsupported protocol version" );
                break;
            case EINVAL:
                NSLog( @"IPC protocol parse error" );
                break;
            default:
                NSLog( @"IPC parsing failed" );
                break;
        }
        [_controller killclient: self];
        return;
    }
    else if( 0 < res )
    {
        assert( res <= [_buf length]);
        if( res < [_buf length])
        {
            memmove( [_buf mutableBytes], [_buf bytes] + res,
                     [_buf length] - res );
        }
        [_buf setLength: ([_buf length] - res)];
    }
}

- (BOOL) sendresp: (uint8_t *) buf
             size: (size_t) size
{
    @try
    {
        [_handle writeData: [NSData dataWithBytesNoCopy: buf
                                                 length: size
                                           freeWhenDone: YES]];
        return YES;
    }
    @catch( NSException * ex )
    {
        NSLog( @"Failed to write to IPC socket connection" );
        return NO;
    }
}

- (BOOL) sendrespEmpty: (enum ipc_msg) msgid
                   tag: (int64_t) tag
{
    uint8_t * buf;
    size_t    size;

    buf = ipc_mkempty( _ipc, &size, msgid, tag );
    if( NULL == buf )
        return NO;

    return [self sendresp: buf
                     size: size];
}

- (BOOL) sendrespInt: (enum ipc_msg) msgid
                 tag: (int64_t) tag
                 val: (int64_t) val
{
    uint8_t * buf;
    size_t    size;

    buf = ipc_mkint( _ipc, &size, msgid, tag, val );
    if( NULL == buf )
        return NO;

    return [self sendresp: buf
                     size: size];
}

- (BOOL) sendrespStr: (enum ipc_msg) msgid
                 tag: (int64_t) tag
                 val: (NSString *) val
{
    uint8_t       * buf;
    size_t          size;
    NSData        * data;
    NSMutableData * sucky;

    if( [val canBeConvertedToEncoding: NSUTF8StringEncoding] )
        buf = ipc_mkstr( _ipc, &size, msgid, tag,
                         [val cStringUsingEncoding: NSUTF8StringEncoding] );
    else
    {
        data = [val dataUsingEncoding: NSUTF8StringEncoding
                 allowLossyConversion: YES];
        /* XXX this sucks, I should add a length argument to ipc_mkstr() */
        sucky = [NSMutableData dataWithData: data];
        [sucky appendBytes: "" length: 1];
        buf = ipc_mkstr( _ipc, &size, msgid, tag, [sucky bytes] );
    }
    if( NULL == buf )
        return NO;

    return [self sendresp: buf
                     size: size];
}

- (void) sendrespInfo: (enum ipc_msg) respid
                  tag: (int64_t) tag
             torrents: (NSArray *) tors
                types: (int) types
{
    benc_val_t     packet, * pkinf;
    NSEnumerator * enumerator;
    Torrent      * tor;
    uint8_t      * buf;
    size_t         size;
    int            res;

    pkinf = ipc_initval( _ipc, respid, tag, &packet, TYPE_LIST );
    if( NULL == pkinf )
        goto fail;
    if( tr_bencListReserve( pkinf, [tors count] ) )
    {
        tr_bencFree( &packet );
        goto fail;
    }

    enumerator = [tors objectEnumerator];
    while( nil != ( tor = [enumerator nextObject] ) )
    {
        if( IPC_MSG_INFO == respid )
            res = ipc_addinfo( pkinf, [tor torrentID], [tor torrentInfo], types );
        else
            res = ipc_addstat( pkinf, [tor torrentID], [tor torrentStat], types );
        if( 0 > res )
        {
            tr_bencFree( &packet );
            goto fail;
        }
    }

    buf = ipc_mkval( &packet, &size );
    tr_bencFree( &packet );
    if( NULL == buf )
        goto fail;
    [self sendresp: buf size: size ];
    return;

  fail:
    NSLog( @"Failed to create IPC reply packet" );
    [_controller killclient: self];
}


@end

void getaddr( struct sockaddr_un * sun )
{
    bzero( sun, sizeof *sun );
    sun->sun_family = AF_LOCAL;
    strlcpy( sun->sun_path, tr_getPrefsDirectory(), sizeof sun->sun_path );
    strlcat( sun->sun_path, "/socket", sizeof sun->sun_path );
}

NSArray * bencarray( benc_val_t * val, int type )
{
    int              ii;
    NSMutableArray * ret;
    benc_val_t     * item;

    assert( TYPE_STR == type || TYPE_INT == type );

    if( NULL == val || TYPE_LIST != val->type )
        return nil;

    ret = [NSMutableArray arrayWithCapacity: val->val.l.count];
    for( ii = 0; ii < val->val.l.count; ii++ )
    {
        item = &val->val.l.vals[ii];
        if( type != item->type )
            return nil;
        if( TYPE_STR == type )
        {
            [ret addObject: [NSString stringWithCString: item->val.s.s
                                               encoding: NSUTF8StringEncoding]];
        }
        else
        {
            [ret addObject: [NSNumber numberWithLongLong: (long long) item->val.i]];
        }
    }

    return ret;
}

void msg_lookup( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient      * client = arg;
    NSArray        * hashes, * tors;
    benc_val_t       packet, * pkinf;
    NSEnumerator   * enumerator;
    Torrent        * tor;
    uint8_t        * buf;
    size_t           size;

    hashes = bencarray( val, TYPE_STR );
    if( NULL == hashes )
    {
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }

    tors  = [[[client controller] delegate] ipcGetTorrentsByHash: hashes];
    [client sendrespInfo: IPC_MSG_INFO
                     tag: tag
                torrents: tors
                   types: IPC_INF_HASH];
}

void msg_info( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient  * client = arg;
    enum ipc_msg respid;
    benc_val_t * typesval;
    int          types;
    NSArray    * ids, * tors;

    if( NULL == val || TYPE_DICT != val->type )
        goto bad;

    typesval = tr_bencDictFind( val, "type" );
    if( NULL == typesval || TYPE_LIST != typesval->type ||
        nil == ( ids = bencarray( tr_bencDictFind( val, "id" ), TYPE_INT ) ) )
        goto bad;

    respid = ( IPC_MSG_GETINFO == msgid ? IPC_MSG_INFO : IPC_MSG_STAT );
    tors   = [[[client controller] delegate] ipcGetTorrentsByID: ids];
    types  = ipc_infotypes( respid, typesval );
    [client sendrespInfo: respid tag: tag torrents: tors types: types];
    return;

  bad:
    NSLog( @"Got bad IPC packet" );
    [client sendrespEmpty: IPC_MSG_BAD tag: tag];
}

void msg_infoall( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient  * client = arg;
    enum ipc_msg respid;
    int          types;
    NSArray    * tors;

    if( NULL == val || TYPE_LIST != val->type )
    {
        NSLog( @"Got bad IPC packet" );
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }

    respid = ( IPC_MSG_GETINFOALL == msgid ? IPC_MSG_INFO : IPC_MSG_STAT );
    tors   = [[[client controller] delegate] ipcGetTorrentsByID: nil];
    types  = ipc_infotypes( respid, val );
    [client sendrespInfo: respid tag: tag torrents: tors types: types];
}

void msg_action( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;
    BOOL        res;
    NSArray   * ids, * tors;
    id          delegate;

    ids = bencarray( val, TYPE_INT );
    if( nil == ids )
    {
        NSLog( @"Got bad IPC packet" );
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }

    delegate = [[client controller] delegate];
    tors     = [delegate ipcGetTorrentsByID: ids];
    switch( msgid )
    {
        case IPC_MSG_REMOVE:
            res = [delegate ipcRemoveTorrents: tors];
            break;
        case IPC_MSG_START:
            res = [delegate ipcStartTorrents: tors];
            break;
        case IPC_MSG_STOP:
            res = [delegate ipcStopTorrents: tors];
            break;
        default:
            assert( 0 );
            return;
    }

    if( res )
        [client sendrespEmpty: IPC_MSG_OK tag: tag];
    else
        [client sendrespEmpty: IPC_MSG_FAIL tag: tag];
}

void msg_actionall( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;
    BOOL        res;
    NSArray   * tors;
    id          delegate;

    delegate = [[client controller] delegate];
    tors     = [delegate ipcGetTorrentsByID: nil];
    switch( msgid )
    {
        case IPC_MSG_REMOVEALL:
            res = [delegate ipcRemoveTorrents: tors];
            break;
        case IPC_MSG_STARTALL:
            res = [delegate ipcStartTorrents: tors];
            break;
        case IPC_MSG_STOPALL:
            res = [delegate ipcStopTorrents: tors];
            break;
        default:
            assert( 0 );
            return;
    }

    if( res )
        [client sendrespEmpty: IPC_MSG_OK tag: tag];
    else
        [client sendrespEmpty: IPC_MSG_FAIL tag: tag];
}

void msg_addold( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;
    NSArray   * paths;
    BOOL        res;

    paths = bencarray( val, TYPE_STR );
    if( nil == paths )
    {
        NSLog( @"Got bad IPC packet" );
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }

    /* XXX should send back info message with torrent IDs */
    if( [[[client controller] delegate] ipcAddTorrents: paths] )
        [client sendrespEmpty: IPC_MSG_OK tag: tag];
    else
        [client sendrespEmpty: IPC_MSG_FAIL tag: tag];
}

void msg_addnew( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient  * client = arg;
    benc_val_t * fileval, * dataval, * dirval, * autoval;
    NSString   * file, * dir;
    NSData     * data;
    BOOL         autobool, res;

    if( NULL == val || TYPE_DICT != val->type )
        goto bad;

    fileval = tr_bencDictFind( val, "file" );
    dataval = tr_bencDictFind( val, "data" );
    dirval  = tr_bencDictFind( val, "directory" );
    autoval = tr_bencDictFind( val, "autostart" );
    if( ( ( NULL == fileval || TYPE_STR != fileval->type )   &&
          ( NULL == dataval || TYPE_STR != dataval->type ) ) ||
        ( ( NULL != fileval && TYPE_STR == fileval->type )   &&
          ( NULL != dataval && TYPE_STR == dataval->type ) ) ||
          ( NULL != dirval  && TYPE_STR != dirval->type  )   ||
          ( NULL != autoval && TYPE_INT != autoval->type ) )
        goto bad;

    dir = ( NULL == dirval ? nil :
            [NSString stringWithCString: dirval->val.s.s
                               encoding: NSUTF8StringEncoding] );
    autobool = ( NULL == autoval || 0 == autoval->val.i ? NO : YES );

    if( NULL != fileval )
    {
        file = [NSString stringWithCString: fileval->val.s.s
                                  encoding: NSUTF8StringEncoding];
        if( NULL == autoval )
            res = [[[client controller] delegate]
                      ipcAddTorrentFile: file
                              directory: dir];
        else
            res = [[[client controller] delegate]
                      ipcAddTorrentFileAutostart: file
                                       directory: dir
                                       autostart: autobool];
    }
    else
    {
        data = [NSData dataWithBytes: dataval->val.s.s
                              length: dataval->val.s.i];
        if( NULL == autoval )
            res = [[[client controller] delegate]
                      ipcAddTorrentData: data
                              directory: dir];
        else
            res = [[[client controller] delegate]
                      ipcAddTorrentDataAutostart: data
                                       directory: dir
                                       autostart: autobool];
    }

    /* XXX should send back info message with torrent ID */
    if( res )
        [client sendrespEmpty: IPC_MSG_OK tag: tag];
    else
        [client sendrespEmpty: IPC_MSG_FAIL tag: tag];
    return;

  bad:
    NSLog( @"Got bad IPC packet" );
    [client sendrespEmpty: IPC_MSG_BAD tag: tag];
}

void msg_getbool( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;
    
    fDefaults = [NSUserDefaults standardUserDefaults];

    switch( msgid )
    {
        case IPC_MSG_GETAUTOMAP:
            [client sendrespInt:IPC_MSG_AUTOMAP tag:tag val:[fDefaults boolForKey:@"NatTraversal"]];
            break;
        case IPC_MSG_GETAUTOSTART:
            [client sendrespInt:IPC_MSG_AUTOSTART tag:tag val:[fDefaults boolForKey:@"AutoStartDownload"]];
            break;
        case IPC_MSG_GETPEX:
            [client sendrespInt:IPC_MSG_PEX tag:tag val:[fDefaults boolForKey:@"PEXGlobal"]];
            break;
        default:
            assert( 0 );
            break;
    }
}

void msg_getint( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;
    
    fDefaults = [NSUserDefaults standardUserDefaults];
    
    int theValue;
    
    switch( msgid )
    {
        case IPC_MSG_GETDOWNLIMIT:
            if ( [fDefaults boolForKey:@"CheckDownload"] )
                theValue = [fDefaults integerForKey:@"DownloadLimit"];
            else
                theValue = -1;
            [client sendrespInt:IPC_MSG_DOWNLIMIT tag:tag val:theValue];
            break;
        case IPC_MSG_GETPORT:
            [client sendrespInt:IPC_MSG_PORT tag:tag val:[fDefaults integerForKey:@"BindPort"]];
            break;
        case IPC_MSG_GETUPLIMIT:
            if ( [fDefaults boolForKey:@"CheckUpload"] )
                theValue = [fDefaults integerForKey:@"UploadLimit"];
            else
                theValue = -1;
            [client sendrespInt:IPC_MSG_UPLIMIT tag:tag val:theValue];
            break;
        default:
            assert( 0 );
            break;
    }
}

void msg_getstr( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;
    NSString    * cryptoValue;

    fDefaults = [NSUserDefaults standardUserDefaults];
    
    switch( msgid )
    {
            
        case IPC_MSG_GETDIR:
            [client sendrespStr:IPC_MSG_DIR tag:tag val:[fDefaults stringForKey:@"DownloadFolder"]];
            break;
            
        case IPC_MSG_GETCRYPTO:
            if ([fDefaults boolForKey: @"EncryptionPrefer"])
                if ([fDefaults boolForKey: @"EncryptionRequire"])
                    cryptoValue = @"required";
                else
                    cryptoValue = @"preferred";
            else
                cryptoValue = @"plaintext";
            
            [client sendrespStr:IPC_MSG_CRYPTO tag:tag val:cryptoValue];
            break;
            
        default:
            assert( 0 );
            break;
    }
}

void msg_setbool( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;

    if( NULL == val || TYPE_INT != val->type )
    {
        NSLog( @"Got bad IPC packet" );
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }
    
    fDefaults = [NSUserDefaults standardUserDefaults];
    
    switch( msgid )
    {
        case IPC_MSG_AUTOMAP:
            [fDefaults setBool:(bool)val->val.i forKey:@"NatTraversal"];
            [fPrefsController setNat:nil];
            [client sendrespEmpty:IPC_MSG_OK tag:tag];
            break;
        case IPC_MSG_AUTOSTART:
            [fDefaults setBool:(bool)val->val.i forKey:@"AutoStartDownload"];
            [client sendrespEmpty:IPC_MSG_OK tag:tag];
            break;
        case IPC_MSG_PEX:
            [fDefaults setBool:(bool)val->val.i forKey:@"PEXGlobal"];
            [fPrefsController setPEX:nil];
            [client sendrespEmpty: IPC_MSG_OK tag: tag];
            break;
        default:
            assert( 0 );
            break;
    }
}

void msg_setint( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;

    if( NULL == val || TYPE_INT != val->type )
    {
        NSLog( @"Got bad IPC packet" );
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }
    
    fDefaults = [NSUserDefaults standardUserDefaults];
    switch( msgid )
    {
        case IPC_MSG_DOWNLIMIT:
            if ( val->val.i < 0 )
                [fDefaults setBool:NO forKey:@"CheckDownload"];
            else
            {
                [fDefaults setBool:YES forKey:@"CheckDownload"];
                [fDefaults setInteger:val->val.i forKey:@"DownloadLimit"];
            }
            [fPrefsController updateLimitFields];
            [fPrefsController applySpeedSettings: nil];
            break;
        case IPC_MSG_PORT:
            [fPrefsController setPort:[NSNumber numberWithInt:val->val.i]];
            [fPrefsController updatePortField];
            break;
        case IPC_MSG_UPLIMIT:
            if ( val->val.i < 0 )
                [fDefaults setBool:NO forKey:@"CheckUpload"];
            else
            {
                [fDefaults setBool:YES forKey:@"CheckUpload"];
                [fDefaults setInteger:val->val.i forKey:@"UploadLimit"];
            }
            [fPrefsController updateLimitFields];
            [fPrefsController applySpeedSettings: nil];
            break;
        default:
            assert( 0 );
            break;
    }
    [client sendrespEmpty:IPC_MSG_OK tag:tag];
}

void msg_setstr( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;

    if( NULL == val || TYPE_STR != val->type )
    {
        NSLog( @"Got bad IPC packet" );
        [client sendrespEmpty: IPC_MSG_BAD tag: tag];
        return;
    }
             
    fDefaults = [NSUserDefaults standardUserDefaults];
             
    switch( msgid )
    {
        case IPC_MSG_DIR:
             [fDefaults setObject:[NSString stringWithCString: val->val.s.s] forKey:@"DownloadFolder"];
             [fDefaults setObject: @"Constant" forKey: @"DownloadChoice"];
             [client sendrespEmpty: IPC_MSG_OK tag: tag];
             break;
            
        case IPC_MSG_CRYPTO:
            if(!strcasecmp(val->val.s.s, "required"))
            {
                [fDefaults setBool:YES  forKey: @"EncryptionPrefer"];
                [fDefaults setBool:YES  forKey: @"EncryptionRequire"];
            }
            
            else if(!strcasecmp(val->val.s.s, "preferred"))
            {
                [fDefaults setBool:YES  forKey: @"EncryptionPrefer"];
                [fDefaults setBool:NO  forKey: @"EncryptionRequire"];
            }
            
            else if(!strcasecmp(val->val.s.s, "plaintext"))
            {
                [fDefaults setBool:NO  forKey: @"EncryptionPrefer"];
                [fDefaults setBool:NO  forKey: @"EncryptionRequire"];
            }
            [fPrefsController setEncryptionMode:nil];
            [client sendrespEmpty: IPC_MSG_OK tag: tag];
            break;
           
        default:
             assert( 0 );
             break;
    }
}

void msg_empty( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;

    switch( msgid )
    {
        case IPC_MSG_NOOP:
            [client sendrespEmpty: IPC_MSG_OK tag: tag];
            break;
        case IPC_MSG_QUIT:
            [[[client controller] delegate] ipcQuit];
            [client sendrespEmpty: IPC_MSG_OK tag: tag];
            break;
        default:
            assert( 0 );
            break;
    }
}

void msg_sup( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient       * client = arg;
    benc_val_t        packet, * pkval, * name;
    struct ipc_info * ipc;
    int               ii;
    enum ipc_msg      found;
    uint8_t         * buf;
    size_t            size;

    if( NULL == val || TYPE_LIST != val->type )
        goto bad;

    ipc   = [client ipc];
    pkval = ipc_initval( ipc, IPC_MSG_SUP, tag, &packet, TYPE_LIST );
    if( NULL == pkval )
        goto fail;
    if( tr_bencListReserve( pkval, val->val.l.count ) )
    {
        tr_bencFree( &packet );
        goto fail;
    }

    for( ii = 0; val->val.l.count > ii; ii++ )
    {
        name = &val->val.l.vals[ii];
        if( NULL == name || TYPE_STR != name->type )
            goto bad;
        found = ipc_msgid( ipc, name->val.s.s );
        if( IPC__MSG_COUNT == found || !ipc_ishandled( ipc, found ) )
        {
            continue;
        }
        tr_bencInitStr( tr_bencListAdd( pkval ),
                        name->val.s.s, name->val.s.i, 1 );
    }

    buf = ipc_mkval( &packet, &size );
    tr_bencFree( &packet );
    if( NULL == buf )
        goto fail;
    [client sendresp: buf size: size ];
    return;

  bad:
    NSLog( @"Got bad IPC packet" );
    [client sendrespEmpty: IPC_MSG_BAD tag: tag];
    return;

  fail:
    NSLog( @"Failed to create IPC reply packet" );
    [[client controller] killclient: client];
}

void msg_default( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg )
{
    IPCClient * client = arg;

    [client sendrespEmpty: IPC_MSG_NOTSUP tag: tag];
}
