/****************************************************************************** * $Id$ * * Copyright (c) 2007-2008 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 #include #include #include #include #include #include "transmission.h" #include "bencode.h" #include "utils.h" #include "ipcparse.h" #define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( (ary)[0] ) ) #define SAFEFREE( ptr ) \ do \ { \ int saved = errno; \ free( ptr ); \ errno = saved; \ } \ while( 0 ) #define SAFEBENCFREE( val ) \ do \ { \ int saved = errno; \ tr_bencFree( val ); \ errno = saved; \ } \ while( 0 ) /* IPC protocol version */ #define PROTO_VERS_MIN ( 1 ) #define PROTO_VERS_MAX ( 2 ) #define MSGVALID( id ) ( IPC__MSG_COUNT > (id) ) #define MSGNAME( id ) ( gl_msgs[(id)].name ) #define DICTPAYLOAD( info ) ( 2 > (info)->vers ) struct ipc_funcs { trd_msgfunc msgs[IPC__MSG_COUNT]; trd_msgfunc def; }; struct ipc_info { struct ipc_funcs * funcs; int vers; }; int ipc_hasvers( const struct ipc_info * inf ) { return inf && ( inf->vers > 0 ); } struct msg { const char * name; const int minvers; const enum ipc_msg id; }; /* these names must be sorted for strcmp() */ static const struct msg gl_msgs[] = { { "addfile-detailed", 2, IPC_MSG_ADDONEFILE }, { "addfiles", 1, IPC_MSG_ADDMANYFILES }, { "automap", 2, IPC_MSG_AUTOMAP }, { "autostart", 2, IPC_MSG_AUTOSTART }, { "bad-format", 2, IPC_MSG_BAD }, { "directory", 2, IPC_MSG_DIR }, { "downlimit", 2, IPC_MSG_DOWNLIMIT }, { "encryption", 2, IPC_MSG_CRYPTO }, { "failed", 2, IPC_MSG_FAIL }, { "get-automap", 2, IPC_MSG_GETAUTOMAP }, { "get-autostart", 2, IPC_MSG_GETAUTOSTART }, { "get-directory", 2, IPC_MSG_GETDIR }, { "get-downlimit", 2, IPC_MSG_GETDOWNLIMIT }, { "get-encryption", 2, IPC_MSG_GETCRYPTO }, { "get-info", 2, IPC_MSG_GETINFO }, { "get-info-all", 2, IPC_MSG_GETINFOALL }, { "get-pex", 2, IPC_MSG_GETPEX }, { "get-port", 2, IPC_MSG_GETPORT }, { "get-status", 2, IPC_MSG_GETSTAT }, { "get-status-all", 2, IPC_MSG_GETSTATALL }, { "get-supported", 2, IPC_MSG_GETSUP }, { "get-uplimit", 2, IPC_MSG_GETUPLIMIT }, { "info", 2, IPC_MSG_INFO }, { "lookup", 2, IPC_MSG_LOOKUP }, { "noop", 2, IPC_MSG_NOOP }, { "not-supported", 2, IPC_MSG_NOTSUP }, { "pex", 2, IPC_MSG_PEX }, { "port", 2, IPC_MSG_PORT }, { "quit", 1, IPC_MSG_QUIT }, { "remove", 2, IPC_MSG_REMOVE }, { "remove-all", 2, IPC_MSG_REMOVEALL }, { "start", 2, IPC_MSG_START }, { "start-all", 2, IPC_MSG_STARTALL }, { "status", 2, IPC_MSG_STAT }, { "stop", 2, IPC_MSG_STOP }, { "stop-all", 2, IPC_MSG_STOPALL }, { "succeeded", 2, IPC_MSG_OK }, { "supported", 2, IPC_MSG_SUP }, { "uplimit", 2, IPC_MSG_UPLIMIT }, { "verify", 2, IPC_MSG_VERIFY }, { "version", 1, IPC_MSG_VERSION } }; struct inf { const char * name; const int type; }; /* these names must be sorted for strcmp() */ static const struct inf gl_inf[] = { { "comment", IPC_INF_COMMENT }, { "creator", IPC_INF_CREATOR }, { "date", IPC_INF_DATE }, { "files", IPC_INF_FILES }, { "hash", IPC_INF_HASH }, { "id", IPC_INF_ID }, { "name", IPC_INF_NAME }, { "path", IPC_INF_PATH }, { "private", IPC_INF_PRIVATE }, { "size", IPC_INF_SIZE }, { "trackers", IPC_INF_TRACKERS } }; /* these names must be sorted for strcmp() */ static const struct inf gl_stat[] = { { "completed", IPC_ST_COMPLETED }, { "download-speed", IPC_ST_DOWNSPEED }, { "download-total", IPC_ST_DOWNTOTAL }, { "download-valid", IPC_ST_DOWNVALID }, { "error", IPC_ST_ERROR }, { "error-message", IPC_ST_ERRMSG }, { "eta", IPC_ST_ETA }, { "id", IPC_ST_ID }, { "peers-downloading", IPC_ST_PEERDOWN }, { "peers-from", IPC_ST_PEERFROM }, { "peers-total", IPC_ST_PEERTOTAL }, { "peers-uploading", IPC_ST_PEERUP }, { "running", IPC_ST_RUNNING }, { "scrape-completed", IPC_ST_TKDONE }, { "scrape-leechers", IPC_ST_TKLEECH }, { "scrape-seeders", IPC_ST_TKSEED }, { "state", IPC_ST_STATE }, { "swarm-speed", IPC_ST_SWARM }, { "tracker", IPC_ST_TRACKER }, { "upload-speed", IPC_ST_UPSPEED }, { "upload-total", IPC_ST_UPTOTAL } }; struct ipc_funcs * ipc_initmsgs( void ) { return tr_new0( struct ipc_funcs, 1 ); } void ipc_addmsg( struct ipc_funcs * funcs, enum ipc_msg msg_id, trd_msgfunc func ) { assert( MSGVALID( msg_id ) ); assert( IPC_MSG_VERSION != msg_id ); funcs->msgs[msg_id] = func; } void ipc_setdefmsg( struct ipc_funcs * funcs, trd_msgfunc func ) { funcs->def = func; } void ipc_freemsgs( struct ipc_funcs * funcs ) { tr_free( funcs ); } struct ipc_info * ipc_newcon( struct ipc_funcs * funcs ) { struct ipc_info * info = tr_new0( struct ipc_info, 1 ); info->funcs = funcs; info->vers = -1; return info; } void ipc_freecon( struct ipc_info * session ) { tr_free( session ); } int ipc_ishandled( const struct ipc_info * session, enum ipc_msg msg_id ) { assert( MSGVALID( msg_id ) ); return session->funcs->msgs[msg_id] != NULL; } int ipc_havetags( const struct ipc_info * session ) { return !DICTPAYLOAD( session ); } static int sessionSupportsTags( const struct ipc_info * session ) { return session->vers >= 2; } static int sessionSupportsMessage( const struct ipc_info * session, enum ipc_msg id ) { assert( MSGVALID( id ) ); assert( ipc_hasvers( session ) ); return gl_msgs[id].minvers <= session->vers; } /** * Creates the benc metainfo structure for a message * and returns its child where payload should be set. * * In protocol version 1, the metainfo is a single-entry * dictionary with a string from gl_msgs as the key * and the return tr_benc pointer as the value. * * In protocol version 2, the metainfo is a list * holding a string from gl_msgs, the return benc pointer, * and (optionally) the integer tag. */ tr_benc * ipc_initval( const struct ipc_info * session, enum ipc_msg msg_id, int64_t tag, tr_benc * pk, int benc_type ) { tr_benc * ret; assert( MSGVALID( msg_id ) ); if( !sessionSupportsMessage( session, msg_id ) || ( (tag>0) && !sessionSupportsTags( session ) ) ) { errno = EPERM; return NULL; } if( DICTPAYLOAD( session ) ) { tr_bencInitDict( pk, 1 ); ret = tr_bencDictAdd( pk, MSGNAME( msg_id ) ); } else { tr_bencInitList( pk, 3 ); tr_bencInitStr( tr_bencListAdd( pk ), MSGNAME( msg_id ), -1, 1 ); ret = tr_bencListAdd( pk ); if( 0 < tag ) tr_bencInitInt( tr_bencListAdd( pk ), tag ); } tr_bencInit( ret, benc_type ); return ret; } /** * Serialize a benc message into a string appended to a * printf()'ed string IPC_MIN_MSG_LEN bytes long that * gives the length of the string. */ uint8_t * ipc_serialize( const tr_benc * benc, size_t * setmeSize ) { uint8_t * ret = NULL; int len = 0; char * str = tr_bencSave( benc, &len ); if( len > IPC_MAX_MSG_LEN ) errno = EFBIG; else { const size_t size = IPC_MIN_MSG_LEN + len; ret = tr_new( uint8_t, size ); snprintf( (char*)ret, size, "%0*X", IPC_MIN_MSG_LEN, len ); memcpy( ret + IPC_MIN_MSG_LEN, str, len ); *setmeSize = size; } tr_free( str ); return ret; } /** * Create a serialized message whose payload is a NULL string */ uint8_t * ipc_mkempty( const struct ipc_info * session, size_t * setmeSize, enum ipc_msg msg_id, int64_t tag ) { return ipc_mkstr( session, setmeSize, msg_id, tag, NULL ); } /** * Create a serialized message whose payload is an integer */ uint8_t * ipc_mkint( const struct ipc_info * session, size_t * setmeSize, enum ipc_msg msg_id, int64_t tag, int64_t num ) { tr_benc pk, * val; uint8_t * ret = NULL; if(( val = ipc_initval( session, msg_id, tag, &pk, TYPE_INT ))) { val->val.i = num; ret = ipc_serialize( &pk, setmeSize ); SAFEBENCFREE( &pk ); } return ret; } /** * Create a serialized message whose payload is a string */ uint8_t * ipc_mkstr( const struct ipc_info * session, size_t * setmeSize, enum ipc_msg msg_id, int64_t tag, const char * str ) { tr_benc pk, * val; uint8_t * ret = NULL; if(( val = ipc_initval( session, msg_id, tag, &pk, TYPE_STR ))) { tr_bencInitStr( val, str, -1, 1 ); ret = ipc_serialize( &pk, setmeSize ); SAFEBENCFREE( &pk ); } return ret; } /** * Create a serialized message whose payload is a dictionary * giving the minimum and maximum protocol version we support, * and (optionally) the label passed in. * * Note that this message is just the dictionary payload. * It doesn't contain metainfo as the other ipc_mk*() functions do. * That's because the metainfo is dependent on the protocol version, * and this is a handshake message to negotiate protocol versions. * * @see handlevers() */ uint8_t * ipc_mkvers( size_t * len, const char * label ) { tr_benc pk, * dict; uint8_t * ret; tr_bencInitDict( &pk, 1 ); dict = tr_bencDictAdd( &pk, MSGNAME( IPC_MSG_VERSION ) ); tr_bencInitDict( dict, 3 ); tr_bencInitInt( tr_bencDictAdd( dict, "min" ), PROTO_VERS_MIN ); tr_bencInitInt( tr_bencDictAdd( dict, "max" ), PROTO_VERS_MAX ); if( label ) tr_bencInitStr( tr_bencDictAdd( dict, "label" ), label, -1, 1 ); ret = ipc_serialize( &pk, len ); SAFEBENCFREE( &pk ); return ret; } /** * Create a serialized message that is used to request * torrent information or statistics. * * msg_id must be one of: * IPC_MSG_GETINFO * IPC_MSG_GETINFOALL * IPC_MSG_GETSTAT * IPC_MSG_GETSTATALL * * "ids" is an optional array of torrent IDs. * The array, if included, must be terminated by a 0 torrent id. * * "types" is a bitwise-and'ed set of fields from either * the IPC_INF_* or IPC_ST_* enums in ipc-parse.h. * Which enums are used is dependent on the value of msg_id. * * If torrent ids are specified in the "ids" array, * the payload is a dictionary of two lists, "id" and "type". * The "id" list holds the torrent IDs, and * the "type" list holds string keys from either * gl_inf or gl_stat, depending on the value of msg_id * * If no torrent ids are specified, the payload is * a single list identical to the "type" list described above. */ uint8_t * ipc_createInfoRequest( const struct ipc_info * session, size_t * setmeSize, enum ipc_msg msg_id, int64_t tag, int types, const int * ids ) { tr_benc pk; tr_benc * typelist; size_t ii, typecount, used; const struct inf * typearray; uint8_t * ret; /* no ID list, send an -all message */ if( !ids ) { typelist = ipc_initval( session, msg_id, tag, &pk, TYPE_LIST ); if( !typelist ) return NULL; } else { tr_benc * top; tr_benc * idlist; top = ipc_initval( session, msg_id, tag, &pk, TYPE_DICT ); if( !top ) return NULL; /* add the requested IDs */ tr_bencDictReserve( top, 2 ); idlist = tr_bencDictAdd( top, "id" ); typelist = tr_bencDictAdd( top, "type" ); tr_bencInit( typelist, TYPE_LIST ); for( ii = 0; TORRENT_ID_VALID( ids[ii] ); ii++ ) { } tr_bencInitList( idlist, ii ); for( ii = 0; TORRENT_ID_VALID( ids[ii] ); ii++ ) tr_bencInitInt( tr_bencListAdd( idlist ), ids[ii] ); } /* get the type name array */ switch( msg_id ) { case IPC_MSG_GETINFO: case IPC_MSG_GETINFOALL: typecount = TR_N_ELEMENTS( gl_inf ); typearray = gl_inf; break; case IPC_MSG_GETSTAT: case IPC_MSG_GETSTATALL: typecount = TR_N_ELEMENTS( gl_stat ); typearray = gl_stat; break; default: assert( 0 ); break; } /* add the type names */ for( ii = used = 0; typecount > ii; ii++ ) if( types & ( 1 << ii ) ) used++; tr_bencListReserve( typelist, used ); for( ii = 0; typecount > ii; ii++ ) { if( !( types & ( 1 << ii ) ) ) continue; assert( typearray[ii].type == ( 1 << ii ) ); tr_bencInitStr( tr_bencListAdd( typelist ), typearray[ii].name, -1, 1 ); } /* generate packet */ ret = ipc_serialize( &pk, setmeSize ); SAFEBENCFREE( &pk ); return ret; } static void filltracker( tr_benc * val, const tr_tracker_info * tk ) { tr_bencInitDict( val, 4 ); tr_bencInitStr( tr_bencDictAdd( val, "address" ), tk->address, -1, 1 ); tr_bencInitInt( tr_bencDictAdd( val, "port" ), tk->port ); tr_bencInitStr( tr_bencDictAdd( val, "announce" ), tk->announce, -1, 1 ); if( tk->scrape ) tr_bencInitStr( tr_bencDictAdd( val, "scrape" ), tk->scrape, -1, 1 ); } /** * append to "list" a dictionary whose keys are * the string keys from gl_inf and whose values are * torrent info set from "torrent_id" and "inf". * * "types" is a bitwise-and'ed set of fields * from the IPC_INF_* enum in ipcparse.h. * It specifies what to put in the dictionary. */ int ipc_addinfo( tr_benc * list, int torrent_id, const tr_info * inf, int types ) { tr_benc * dict; int ii, jj, kk; tr_file_index_t ff; /* always send torrent id */ types |= IPC_INF_ID; tr_bencListReserve( list, 1 ); dict = tr_bencListAdd( list ); /* count the number of info keys and allocate a dict for them */ for( ii = jj = 0; IPC_INF__MAX > 1 << ii; ii++ ) { if( !( types & ( 1 << ii ) ) ) continue; assert( TR_N_ELEMENTS( gl_inf ) > ( unsigned )ii ); assert( gl_inf[ii].type == ( 1 << ii ) ); jj++; } tr_bencInitDict( dict, jj ); /* populate the dict with info key->value pairs */ for( ii = 0; IPC_INF__MAX > 1 << ii; ii++ ) { tr_benc * item; if( !( types & ( 1 << ii ) ) ) continue; item = tr_bencDictAdd( dict, gl_inf[ii].name ); switch( 1 << ii ) { case IPC_INF_COMMENT: tr_bencInitStr( item, inf->comment ? inf->comment : "", -1, 1 ); break; case IPC_INF_CREATOR: tr_bencInitStr( item, inf->creator ? inf->creator : "", -1, 1 ); break; case IPC_INF_DATE: tr_bencInitInt( item, inf->dateCreated ); break; case IPC_INF_FILES: tr_bencInitList( item, inf->fileCount ); for( ff = 0; inf->fileCount > ff; ff++ ) { tr_benc * file = tr_bencListAdd( item ); tr_bencInitDict( file, 2 ); tr_bencInitStr( tr_bencDictAdd( file, "name" ), inf->files[ff].name, -1, 1 ); tr_bencInitInt( tr_bencDictAdd( file, "size" ), inf->files[ff].length ); } break; case IPC_INF_HASH: tr_bencInitStr( item, inf->hashString, -1, 1 ); break; case IPC_INF_ID: tr_bencInitInt( item, torrent_id ); break; case IPC_INF_NAME: tr_bencInitStr( item, inf->name, -1, 1 ); break; case IPC_INF_PATH: tr_bencInitStr( item, inf->torrent, -1, 1 ); break; case IPC_INF_PRIVATE: tr_bencInitInt( item, inf->isPrivate ? 1 : 0 ); break; case IPC_INF_SIZE: tr_bencInitInt( item, inf->totalSize ); break; case IPC_INF_TRACKERS: tr_bencInitList( item, inf->trackerTiers ); for( jj = 0; inf->trackerTiers > jj; jj++ ) { tr_benc * tier = tr_bencListAdd( item ); tr_bencInitList( tier, inf->trackerList[jj].count ); for( kk = 0; inf->trackerList[jj].count > kk; kk++ ) filltracker( tr_bencListAdd( tier ), &inf->trackerList[jj].list[kk] ); } break; default: assert( 0 ); break; } } return 0; } /** * append to "list" a dictionary whose keys are * the string keys from gl_stat and whose values * are torrent statistics set from "st". * * "types" is a bitwise-and'ed set of fields * from the IPC_INF_* enum in ipcparse.h. * It specifies what to put in the dictionary. */ int ipc_addstat( tr_benc * list, int torrent_id, const tr_stat * st, int types ) { tr_benc * dict; int ii, used; /* add the dictionary child */ tr_bencListReserve( list, 1 ); dict = tr_bencListAdd( list ); /* always send torrent id */ types |= IPC_ST_ID; /* count the number of stat keys and allocate a dict for them */ for( ii = used = 0; IPC_ST__MAX > 1 << ii; ii++ ) if( types & ( 1 << ii ) ) used++; tr_bencInitDict( dict, used ); /* populate the dict */ for( ii = 0; IPC_ST__MAX > 1 << ii; ii++ ) { tr_benc * item; if( !( types & ( 1 << ii ) ) ) continue; assert( TR_N_ELEMENTS( gl_stat ) > ( unsigned )ii ); assert( gl_stat[ii].type == ( 1 << ii ) ); item = tr_bencDictAdd( dict, gl_stat[ii].name ); switch( 1 << ii ) { case IPC_ST_COMPLETED: case IPC_ST_DOWNVALID: tr_bencInitInt( item, st->haveValid ); break; case IPC_ST_DOWNSPEED: tr_bencInitInt( item, st->rateDownload * 1024 ); break; case IPC_ST_DOWNTOTAL: tr_bencInitInt( item, st->downloadedEver ); break; case IPC_ST_ERROR: { const tr_errno error = st->error; if( TR_OK == error ) { tr_bencInitStr( item, "", -1, 1 ); } else if( error == TR_ERROR_ASSERT ) { tr_bencInitStr( item, "assert", -1, 1 ); } else if( error == TR_ERROR_IO_PERMISSIONS ) { tr_bencInitStr( item, "io-permissions", -1, 1 ); } else if( error == TR_ERROR_IO_SPACE ) { tr_bencInitStr( item, "io-space", -1, 1 ); } else if( error == TR_ERROR_IO_FILE_TOO_BIG ) { tr_bencInitStr( item, "io-file-too-big", -1, 1 ); } else if( error == TR_ERROR_IO_OPEN_FILES ) { tr_bencInitStr( item, "io-open-files", -1, 1 ); } else if( TR_ERROR_IS_IO( error ) ) { tr_bencInitStr( item, "io-other", -1, 1 ); } else if( error == TR_ERROR_TC_ERROR ) { tr_bencInitStr( item, "tracker-error", -1, 1 ); } else if( error == TR_ERROR_TC_WARNING ) { tr_bencInitStr( item, "tracker-warning", -1, 1 ); } else if( TR_ERROR_IS_TC( error ) ) { tr_bencInitStr( item, "tracker-other", -1, 1 ); } else { tr_bencInitStr( item, "other", -1, 1 ); } break; } case IPC_ST_ERRMSG: if( TR_OK == st->error ) { tr_bencInitStr( item, "", -1, 1 ); } else if( '\0' == st->errorString[0] ) { tr_bencInitStr( item, "other", -1, 1 ); } else if( tr_bencInitStrDup( item, st->errorString ) ) { return -1; } break; case IPC_ST_ETA: tr_bencInitInt( item, st->eta ); break; case IPC_ST_ID: tr_bencInitInt( item, torrent_id ); break; case IPC_ST_PEERDOWN: tr_bencInitInt( item, st->peersSendingToUs ); break; case IPC_ST_PEERFROM: tr_bencInitDict( item, 4 ); tr_bencInitInt( tr_bencDictAdd( item, "incoming" ), st->peersFrom[TR_PEER_FROM_INCOMING] ); tr_bencInitInt( tr_bencDictAdd( item, "tracker" ), st->peersFrom[TR_PEER_FROM_TRACKER] ); tr_bencInitInt( tr_bencDictAdd( item, "cache" ), st->peersFrom[TR_PEER_FROM_CACHE] ); tr_bencInitInt( tr_bencDictAdd( item, "pex" ), st->peersFrom[TR_PEER_FROM_PEX] ); break; case IPC_ST_PEERTOTAL: tr_bencInitInt( item, st->peersConnected ); break; case IPC_ST_PEERUP: tr_bencInitInt( item, st->peersGettingFromUs ); break; case IPC_ST_RUNNING: tr_bencInitInt( item, TR_STATUS_IS_ACTIVE(st->status) ); break; case IPC_ST_STATE: if( TR_STATUS_CHECK_WAIT & st->status ) { tr_bencInitStr( item, "waiting to checking", -1, 1 ); } else if( TR_STATUS_CHECK & st->status ) { tr_bencInitStr( item, "checking", -1, 1 ); } else if( TR_STATUS_DOWNLOAD & st->status ) { tr_bencInitStr( item, "downloading", -1, 1 ); } else if( TR_STATUS_SEED & st->status ) { tr_bencInitStr( item, "seeding", -1, 1 ); } else if( TR_STATUS_STOPPED & st->status ) { tr_bencInitStr( item, "paused", -1, 1 ); } else { assert( 0 ); } break; case IPC_ST_SWARM: tr_bencInitInt( item, st->swarmspeed * 1024 ); break; case IPC_ST_TRACKER: filltracker( item, st->tracker ); break; case IPC_ST_TKDONE: tr_bencInitInt( item, st->completedFromTracker ); break; case IPC_ST_TKLEECH: tr_bencInitInt( item, st->leechers ); break; case IPC_ST_TKSEED: tr_bencInitInt( item, st->seeders ); break; case IPC_ST_UPSPEED: tr_bencInitInt( item, st->rateUpload * 1024 ); break; case IPC_ST_UPTOTAL: tr_bencInitInt( item, st->uploadedEver ); break; default: assert( 0 ); break; } } return 0; } /** * This reads a handshake message from the client to decide * which IPC protocol version to use. * Returns 0 on success; otherwise, returns -1 and sets errno. * * @see ipc_handleMessages() * @see ipc_mkvers() */ static int handlevers( struct ipc_info * info, tr_benc * dict ) { tr_benc * vers; int64_t min, max; if( !tr_bencIsDict( dict ) ) { errno = EINVAL; return -1; } vers = tr_bencDictFind( dict, MSGNAME( IPC_MSG_VERSION ) ); if( NULL == vers ) { errno = EINVAL; return -1; } switch( vers->type ) { case TYPE_INT: min = vers->val.i; max = vers->val.i; break; case TYPE_DICT: { tr_benc * num = tr_bencDictFind( vers, "min" ); min = tr_bencIsInt( num ) ? num->val.i : -1; num = tr_bencDictFind( vers, "max" ); max = tr_bencIsInt( num ) ? num->val.i : -1; break; } default: min = -1; max = -1; break; } if( 0 >= min || 0 >= max || INT_MAX < min || INT_MAX < max ) { errno = EINVAL; return -1; } assert( PROTO_VERS_MIN <= PROTO_VERS_MAX ); if( min > max ) { errno = EINVAL; return -1; } if( PROTO_VERS_MAX < min || PROTO_VERS_MIN > max ) { errno = EPERM; return -1; } info->vers = MIN( PROTO_VERS_MAX, max ); return 0; } static int compareNameToMsg( const void * a, const void * b ) { const struct msg * msg = b; return strcmp( a, msg->name ); } static const struct msg * msglookup( const char * name ) { return bsearch( name, gl_msgs, TR_N_ELEMENTS( gl_msgs ), sizeof( struct msg ), compareNameToMsg ); } enum ipc_msg ipc_msgid( const struct ipc_info * info, const char * name ) { const struct msg * msg = msglookup( name ); return msg && sessionSupportsMessage( info, msg->id ) ? msg->id : IPC__MSG_COUNT; } /** * Invokes the trd_msgfunc for the message passed in. * Returns 0 on success; otherwise, returns -1 and sets errno. */ static int callmsgfunc( const struct ipc_info * info, tr_benc * name, tr_benc * val, tr_benc * tagval, void * user_data ) { const struct msg * msg; int64_t tag; /* extract tag from tagval */ if( !tagval ) tag = -1; else if( tr_bencIsInt( tagval ) ) tag = tagval->val.i; else { errno = EINVAL; return -1; } /* find the msg corresponding to `name' */ if( !tr_bencIsString( name ) ) { errno = EINVAL; return -1; } msg = msglookup( name->val.s.s ); if( msg && msg->minvers <= info->vers ) { if( info->funcs->msgs[msg->id] != NULL ) { (*info->funcs->msgs[msg->id])( msg->id, val, tag, user_data ); } else if( info->funcs->def ) { info->funcs->def( msg->id, val, tag, user_data ); } } else if( NULL != info->funcs->def ) info->funcs->def( IPC__MSG_UNKNOWN, NULL, tag, user_data ); return 0; } static int handlemsgs( const struct ipc_info * session, tr_benc * message, void * user_data ) { tr_benc * name, * val, * tag; assert( ipc_hasvers( session ) ); if( DICTPAYLOAD( session ) ) { int ii; if( TYPE_DICT != message->type || message->val.l.count % 2 ) { errno = EINVAL; return -1; } for( ii = 0; ii < message->val.l.count; ii += 2 ) { assert( ii + 1 < message->val.l.count ); name = &message->val.l.vals[ii]; val = &message->val.l.vals[ii+1]; if( 0 > callmsgfunc( session, name, val, NULL, user_data ) ) return -1; } } else { if( TYPE_LIST != message->type || 2 > message->val.l.count ) { errno = EINVAL; return -1; } name = &message->val.l.vals[0]; val = &message->val.l.vals[1]; tag = ( 2 == message->val.l.count ? NULL : &message->val.l.vals[2] ); if( 0 > callmsgfunc( session, name, val, tag, user_data ) ) return -1; } return 0; } ssize_t ipc_handleMessages( struct ipc_info * info, const uint8_t * msgs, ssize_t msgslen, void * user_data ) { char hex[IPC_MIN_MSG_LEN+1], * end; ssize_t off, len; tr_benc benc; for( off = 0; off + IPC_MIN_MSG_LEN < msgslen; off += IPC_MIN_MSG_LEN + len ) { memcpy( hex, msgs + off, IPC_MIN_MSG_LEN ); hex[IPC_MIN_MSG_LEN] = '\0'; end = NULL; len = strtol( hex, &end, 16 ); if( hex + IPC_MIN_MSG_LEN != end || 0 > len || IPC_MAX_MSG_LEN < len ) { errno = EINVAL; return -1; } if( off + IPC_MIN_MSG_LEN + len > msgslen ) { break; } errno = 0; if( tr_bencLoad( msgs + off + IPC_MIN_MSG_LEN, len, &benc, NULL ) ) { if( 0 == errno ) { errno = EINVAL; } return -1; } if( 0 > ( ipc_hasvers( info ) ? handlemsgs( info, &benc, user_data ) : handlevers( info, &benc ) ) ) { SAFEBENCFREE( &benc ); return -1; } tr_bencFree( &benc ); } return off; } static int compareNameToInf( const void * a, const void * b ) { const struct inf * inf = b; return strcmp( a, inf->name ); } /** * Convert a benc list of string keys from gl_inf or gl_stat * into a bitwise-or'ed int representation. * msg_id must be either IPC_MSG_INFO or IPC_MSG_STAT. * @see ipc_infoname() */ int ipc_infotypes( enum ipc_msg id, const tr_benc * list ) { const struct inf * array; size_t len; int i; int ret; switch( id ) { case IPC_MSG_INFO: array = gl_inf; len = TR_N_ELEMENTS( gl_inf ); break; case IPC_MSG_STAT: array = gl_stat; len = TR_N_ELEMENTS( gl_stat ); break; default: assert( 0 ); break; } ret = IPC_INF_ID; if( !tr_bencIsList( list ) ) return ret; for( i=0; ival.l.count; ++i ) { const tr_benc * name = &list->val.l.vals[i]; const struct inf * inf; if( !tr_bencIsString( name ) ) continue; inf = bsearch( name->val.s.s, array, len, sizeof( struct inf ), compareNameToInf ); if( inf ) ret |= inf->type; } return ret; } /** * This function is the reverse of ipc_infotypes: * it returns the string key that corresponds to the type passed in. * Type is one of the IPC_INF_* or IPC_ST_* enums from ipcparse.h. * msg_id must be either IPC_MSG_INFO or IPC_MSG_STAT. * @see ipc_infotypes() */ const char * ipc_infoname( enum ipc_msg id, int type ) { const struct inf * array; size_t len, ii; switch( id ) { case IPC_MSG_INFO: array = gl_inf; len = TR_N_ELEMENTS( gl_inf ); break; case IPC_MSG_STAT: array = gl_stat; len = TR_N_ELEMENTS( gl_stat ); break; default: assert( 0 ); break; } for( ii = 0; len > ii; ii++ ) if( array[ii].type == type ) return array[ii].name; assert( 0 ); return NULL; }