source: trunk/libtransmission/ipcparse.c @ 5673

Last change on this file since 5673 was 5673, checked in by charles, 14 years ago

#377: preliminary https support. this commit probably breaks mac and cli and is not for the faint of heart.

  • Property svn:keywords set to Date Rev Author Id
File size: 34.2 KB
Line 
1/******************************************************************************
2 * $Id: ipcparse.c 5673 2008-04-24 01:42:53Z charles $
3 *
4 * Copyright (c) 2007-2008 Joshua Elsasser
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <assert.h>
26#include <errno.h>
27#include <limits.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31
32#include "transmission.h"
33#include "bencode.h"
34#include "torrent.h"
35#include "utils.h"
36
37#include "ipcparse.h"
38
39#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( (ary)[0] ) )
40
41#define SAFEFREE( ptr )      \
42    do                       \
43    {                        \
44        int saved = errno;   \
45        free( ptr );         \
46        errno = saved;       \
47    }                        \
48    while( 0 )
49
50#define SAFEBENCFREE( val )  \
51    do                       \
52    {                        \
53        int saved = errno;   \
54        tr_bencFree( val );  \
55        errno = saved;       \
56    }                        \
57    while( 0 )
58
59/* IPC protocol version */
60#define PROTO_VERS_MIN          ( 1 )
61#define PROTO_VERS_MAX          ( 2 )
62
63#define MSGVALID( id )          ( IPC__MSG_COUNT > (id) )
64#define MSGNAME( id )           ( gl_msgs[(id)].name )
65#define DICTPAYLOAD( info )     ( 2 > (info)->vers )
66
67struct ipc_funcs
68{
69    trd_msgfunc msgs[IPC__MSG_COUNT];
70    trd_msgfunc def;
71};
72
73struct ipc_info
74{
75    struct ipc_funcs * funcs;
76    int                vers;
77};
78
79int
80ipc_hasvers( const struct ipc_info * inf )
81{
82    return inf && ( inf->vers > 0 );
83}
84
85struct msg
86{
87    const char        * name;
88    const int           minvers;
89    const enum ipc_msg  id;
90};
91
92/* these names must be sorted for strcmp() */
93static const struct msg gl_msgs[] =
94{
95    { "addfile-detailed",    2, IPC_MSG_ADDONEFILE    },
96    { "addfiles",            1, IPC_MSG_ADDMANYFILES  },
97    { "automap",             2, IPC_MSG_AUTOMAP       },
98    { "autostart",           2, IPC_MSG_AUTOSTART     },
99    { "bad-format",          2, IPC_MSG_BAD           },
100    { "directory",           2, IPC_MSG_DIR           },
101    { "downlimit",           2, IPC_MSG_DOWNLIMIT     },
102    { "encryption",          2, IPC_MSG_CRYPTO        },
103    { "failed",              2, IPC_MSG_FAIL          },
104    { "get-automap",         2, IPC_MSG_GETAUTOMAP    },
105    { "get-autostart",       2, IPC_MSG_GETAUTOSTART  },
106    { "get-directory",       2, IPC_MSG_GETDIR        },
107    { "get-downlimit",       2, IPC_MSG_GETDOWNLIMIT  },
108    { "get-encryption",      2, IPC_MSG_GETCRYPTO     },
109    { "get-info",            2, IPC_MSG_GETINFO       },
110    { "get-info-all",        2, IPC_MSG_GETINFOALL    },
111    { "get-pex",             2, IPC_MSG_GETPEX        },
112    { "get-port",            2, IPC_MSG_GETPORT       },
113    { "get-status",          2, IPC_MSG_GETSTAT       },
114    { "get-status-all",      2, IPC_MSG_GETSTATALL    },
115    { "get-supported",       2, IPC_MSG_GETSUP        },
116    { "get-uplimit",         2, IPC_MSG_GETUPLIMIT    },
117    { "info",                2, IPC_MSG_INFO          },
118    { "lookup",              2, IPC_MSG_LOOKUP        },
119    { "noop",                2, IPC_MSG_NOOP          },
120    { "not-supported",       2, IPC_MSG_NOTSUP        },
121    { "peer-max",            2, IPC_MSG_PEERMAX       },
122    { "pex",                 2, IPC_MSG_PEX           },
123    { "port",                2, IPC_MSG_PORT          },
124    { "quit",                1, IPC_MSG_QUIT          },
125    { "remove",              2, IPC_MSG_REMOVE        },
126    { "remove-all",          2, IPC_MSG_REMOVEALL     },
127    { "start",               2, IPC_MSG_START         },
128    { "start-all",           2, IPC_MSG_STARTALL      },
129    { "status",              2, IPC_MSG_STAT          },
130    { "stop",                2, IPC_MSG_STOP          },
131    { "stop-all",            2, IPC_MSG_STOPALL       },
132    { "succeeded",           2, IPC_MSG_OK            },
133    { "supported",           2, IPC_MSG_SUP           },
134    { "uplimit",             2, IPC_MSG_UPLIMIT       },
135    { "verify",              2, IPC_MSG_VERIFY        },
136    { "version",             1, IPC_MSG_VERSION       }
137};
138
139struct inf
140{
141    const char    * name;
142    const int       type;
143};
144
145/* these names must be sorted for strcmp() */
146static const struct inf gl_inf[] =
147{
148    { "comment",                IPC_INF_COMMENT   },
149    { "creator",                IPC_INF_CREATOR   },
150    { "date",                   IPC_INF_DATE      },
151    { "files",                  IPC_INF_FILES     },
152    { "hash",                   IPC_INF_HASH      },
153    { "id",                     IPC_INF_ID        },
154    { "name",                   IPC_INF_NAME      },
155    { "path",                   IPC_INF_PATH      },
156    { "private",                IPC_INF_PRIVATE   },
157    { "size",                   IPC_INF_SIZE      },
158    { "trackers",               IPC_INF_TRACKERS  }
159};
160
161/* these names must be sorted for strcmp() */
162static const struct inf gl_stat[] =
163{
164    { "completed",              IPC_ST_COMPLETED },
165    { "download-speed",         IPC_ST_DOWNSPEED },
166    { "download-total",         IPC_ST_DOWNTOTAL },
167    { "download-valid",         IPC_ST_DOWNVALID },
168    { "error",                  IPC_ST_ERROR     },
169    { "error-message",          IPC_ST_ERRMSG    },
170    { "eta",                    IPC_ST_ETA       },
171    { "id",                     IPC_ST_ID        },
172    { "peers-downloading",      IPC_ST_PEERDOWN  },
173    { "peers-from",             IPC_ST_PEERFROM  },
174    { "peers-max",              IPC_ST_PEERMAX   },
175    { "peers-total",            IPC_ST_PEERTOTAL },
176    { "peers-uploading",        IPC_ST_PEERUP    },
177    { "running",                IPC_ST_RUNNING   },
178    { "scrape-completed",       IPC_ST_TKDONE    },
179    { "scrape-leechers",        IPC_ST_TKLEECH   },
180    { "scrape-seeders",         IPC_ST_TKSEED    },
181    { "state",                  IPC_ST_STATE     },
182    { "swarm-speed",            IPC_ST_SWARM     },
183    { "tracker",                IPC_ST_TRACKER   },
184    { "upload-speed",           IPC_ST_UPSPEED   },
185    { "upload-total",           IPC_ST_UPTOTAL   }
186};
187
188struct ipc_funcs *
189ipc_initmsgs( void )
190{
191    return tr_new0( struct ipc_funcs, 1 );
192}
193
194void
195ipc_addmsg( struct ipc_funcs  * funcs,
196            enum ipc_msg        msg_id,
197            trd_msgfunc         func )
198{
199    assert( MSGVALID( msg_id ) );
200    assert( IPC_MSG_VERSION != msg_id );
201
202    funcs->msgs[msg_id] = func;
203}
204
205void
206ipc_setdefmsg( struct ipc_funcs * funcs, trd_msgfunc func )
207{
208    funcs->def = func;
209}
210
211void
212ipc_freemsgs( struct ipc_funcs * funcs )
213{
214    tr_free( funcs );
215}
216
217struct ipc_info *
218ipc_newcon( struct ipc_funcs * funcs )
219{
220    struct ipc_info * info = tr_new0( struct ipc_info, 1 );
221    info->funcs = funcs;
222    info->vers  = -1;
223    return info;
224}
225
226void
227ipc_freecon( struct ipc_info * session )
228{
229    tr_free( session );
230}
231
232int
233ipc_ishandled( const struct ipc_info * session, enum ipc_msg msg_id )
234{
235    assert( MSGVALID( msg_id ) );
236
237    return session->funcs->msgs[msg_id] != NULL;
238}
239
240int
241ipc_havetags( const struct ipc_info * session )
242{
243    return !DICTPAYLOAD( session );
244}
245
246static int
247sessionSupportsTags( const struct ipc_info * session )
248{
249    return session->vers >= 2;
250}
251
252static int
253sessionSupportsMessage( const struct ipc_info * session, enum ipc_msg id )
254{
255    assert( MSGVALID( id ) );
256    assert( ipc_hasvers( session ) );
257
258    return gl_msgs[id].minvers <= session->vers;
259}
260
261/**
262 * Creates the benc metainfo structure for a message
263 * and returns its child where payload should be set.
264 *
265 * In protocol version 1, the metainfo is a single-entry
266 * dictionary with a string from gl_msgs as the key
267 * and the return tr_benc pointer as the value.
268 *
269 * In protocol version 2, the metainfo is a list
270 * holding a string from gl_msgs, the return benc pointer,
271 * and (optionally) the integer tag.
272 */
273tr_benc *
274ipc_initval( const struct ipc_info * session,
275             enum ipc_msg            msg_id,
276             int64_t                 tag,
277             tr_benc               * pk,
278             int                     benc_type )
279{
280    tr_benc * ret;
281
282    assert( MSGVALID( msg_id ) );
283
284    if( !sessionSupportsMessage( session, msg_id )
285        || ( (tag>0) && !sessionSupportsTags( session ) ) )
286    {
287        errno = EPERM;
288        return NULL;
289    }
290
291    if( DICTPAYLOAD( session ) )
292    {
293        tr_bencInitDict( pk, 1 );
294        ret = tr_bencDictAdd( pk, MSGNAME( msg_id ) );
295    }
296    else
297    {
298        tr_bencInitList( pk, 3 );
299        tr_bencListAddStr( pk, MSGNAME( msg_id ) );
300        ret = tr_bencListAdd( pk );
301        if( 0 < tag )
302            tr_bencListAddInt( pk, tag );
303    }
304
305    tr_bencInit( ret, benc_type );
306    return ret;
307}
308
309/**
310 * Serialize a benc message into a string appended to a
311 * printf()'ed string IPC_MIN_MSG_LEN bytes long that
312 * gives the length of the string.
313 */
314uint8_t *
315ipc_serialize( const tr_benc * benc, size_t * setmeSize )
316{
317    uint8_t * ret = NULL;
318    int len = 0;
319    char * str = tr_bencSave( benc, &len );
320
321    if( len > IPC_MAX_MSG_LEN )
322        errno = EFBIG;
323    else {
324        const size_t size = IPC_MIN_MSG_LEN + len;
325        ret = tr_new( uint8_t, size );
326        snprintf( (char*)ret, size, "%0*X", IPC_MIN_MSG_LEN, len );
327        memcpy( ret + IPC_MIN_MSG_LEN, str, len );
328        *setmeSize = size;
329    }
330
331    tr_free( str );
332    return ret;
333}
334
335/**
336 * Create a serialized message whose payload is a NULL string
337 */
338uint8_t *
339ipc_mkempty( const struct ipc_info * session,
340             size_t                * setmeSize,
341             enum ipc_msg            msg_id,
342             int64_t                 tag )
343{
344    return ipc_mkstr( session, setmeSize, msg_id, tag, NULL );
345}
346
347/**
348 * Create a serialized message whose payload is an integer
349 */
350uint8_t *
351ipc_mkint( const struct ipc_info  * session,
352           size_t                 * setmeSize,
353           enum ipc_msg             msg_id,
354           int64_t                  tag,
355           int64_t                  num )
356{
357    tr_benc pk, * val;
358    uint8_t  * ret = NULL;
359
360    if(( val = ipc_initval( session, msg_id, tag, &pk, TYPE_INT )))
361    {
362        val->val.i = num;
363        ret = ipc_serialize( &pk, setmeSize );
364        SAFEBENCFREE( &pk );
365    }
366
367    return ret;
368}
369
370/**
371 * Create a serialized message whose payload is a string
372 */
373uint8_t *
374ipc_mkstr( const struct ipc_info  * session,
375           size_t                 * setmeSize,
376           enum ipc_msg             msg_id,
377           int64_t                  tag,
378           const char             * str )
379{
380    tr_benc pk, * val;
381    uint8_t  * ret = NULL;
382
383    if(( val = ipc_initval( session, msg_id, tag, &pk, TYPE_STR )))
384    {
385        tr_bencInitStr( val, str, -1, 1 );
386        ret = ipc_serialize( &pk, setmeSize );
387        SAFEBENCFREE( &pk );
388    }
389
390    return ret;
391}
392
393/**
394 * Create a serialized message whose payload is a dictionary
395 * giving the minimum and maximum protocol version we support,
396 * and (optionally) the label passed in.
397 *
398 * Note that this message is just the dictionary payload.
399 * It doesn't contain metainfo as the other ipc_mk*() functions do.
400 * That's because the metainfo is dependent on the protocol version,
401 * and this is a handshake message to negotiate protocol versions.
402 *
403 * @see handlevers()
404 */
405uint8_t *
406ipc_mkvers( size_t * len, const char * label )
407{
408    tr_benc pk, * dict;
409    uint8_t  * ret;
410 
411    tr_bencInitDict( &pk, 1 );
412    dict = tr_bencDictAddDict( &pk, MSGNAME( IPC_MSG_VERSION ), 3 );
413    tr_bencDictAddInt( dict, "min", PROTO_VERS_MIN );
414    tr_bencDictAddInt( dict, "max", PROTO_VERS_MAX );
415    if( label )
416        tr_bencDictAddStr( dict, "label", label );
417
418    ret = ipc_serialize( &pk, len );
419    SAFEBENCFREE( &pk );
420
421    return ret;
422}
423
424/**
425 * Create a serialized message that is used to request
426 * torrent information or statistics.
427 *
428 * msg_id must be one of:
429 *   IPC_MSG_GETINFO
430 *   IPC_MSG_GETINFOALL
431 *   IPC_MSG_GETSTAT
432 *   IPC_MSG_GETSTATALL
433 *
434 * "ids" is an optional array of torrent IDs.
435 * The array, if included, must be terminated by a 0 torrent id.
436 *
437 * "types" is a bitwise-and'ed set of fields from either
438 * the IPC_INF_* or IPC_ST_* enums in ipc-parse.h.
439 * Which enums are used is dependent on the value of msg_id.
440 *
441 * If torrent ids are specified in the "ids" array,
442 * the payload is a dictionary of two lists, "id" and "type".
443 * The "id" list holds the torrent IDs, and
444 * the "type" list holds string keys from either
445 * gl_inf or gl_stat, depending on the value of msg_id
446 *
447 * If no torrent ids are specified, the payload is
448 * a single list identical to the "type" list described above.
449 */
450uint8_t *
451ipc_createInfoRequest( const struct ipc_info * session,
452                       size_t                * setmeSize,
453                       enum ipc_msg            msg_id,
454                       int64_t                 tag,
455                       int                     types,
456                       const int             * ids )
457{
458    tr_benc   pk;
459    tr_benc * typelist;
460    size_t       ii, typecount, used;
461    const struct inf * typearray;
462    uint8_t    * ret;
463
464    /* no ID list, send an -all message */
465    if( !ids ) {
466        typelist = ipc_initval( session, msg_id, tag, &pk, TYPE_LIST );
467        if( !typelist )
468            return NULL;
469    }
470    else
471    {
472        tr_benc * top;
473        tr_benc * idlist;
474
475        top = ipc_initval( session, msg_id, tag, &pk, TYPE_DICT );
476        if( !top )
477            return NULL;
478
479        /* add the requested IDs */
480        tr_bencDictReserve( top, 2 );
481        idlist   = tr_bencDictAdd( top, "id" );
482        typelist = tr_bencDictAdd( top, "type" );
483        tr_bencInit( typelist, TYPE_LIST );
484        for( ii = 0; TORRENT_ID_VALID( ids[ii] ); ii++ ) { }
485        tr_bencInitList( idlist, ii );
486        for( ii = 0; TORRENT_ID_VALID( ids[ii] ); ii++ )
487            tr_bencListAddInt( idlist, ids[ii] );
488    }
489
490    /* get the type name array */
491    switch( msg_id )
492    {
493        case IPC_MSG_GETINFO:
494        case IPC_MSG_GETINFOALL:
495            typecount = TR_N_ELEMENTS( gl_inf );
496            typearray = gl_inf;
497            break;
498        case IPC_MSG_GETSTAT:
499        case IPC_MSG_GETSTATALL:
500            typecount = TR_N_ELEMENTS( gl_stat );
501            typearray = gl_stat;
502            break;
503        default:
504            assert( 0 );
505            break;
506    }       
507
508    /* add the type names */
509    for( ii = used = 0; typecount > ii; ii++ )
510        if( types & ( 1 << ii ) )
511            used++;
512    tr_bencListReserve( typelist, used );
513
514    for( ii = 0; typecount > ii; ii++ )
515    {
516        if( !( types & ( 1 << ii ) ) )
517            continue;
518        assert( typearray[ii].type == ( 1 << ii ) );
519        tr_bencInitStr( tr_bencListAdd( typelist ),
520                        typearray[ii].name, -1, 1 );
521    }
522
523    /* generate packet */
524    ret = ipc_serialize( &pk, setmeSize );
525    SAFEBENCFREE( &pk );
526
527    return ret;
528}
529
530static void
531filltracker( tr_benc * val, const tr_tracker_info * tk )
532{
533    tr_bencInitDict( val, 4 );
534    tr_bencDictAddStr( val, "announce", tk->announce );
535    if( tk->scrape )
536        tr_bencDictAddStr( val, "scrape", tk->scrape );
537}
538
539/**
540 * append to "list" a dictionary whose keys are
541 * the string keys from gl_inf and whose values are
542 * torrent info set from "torrent_id" and "inf".
543 *
544 * "types" is a bitwise-and'ed set of fields
545 * from the IPC_INF_* enum in ipcparse.h.
546 * It specifies what to put in the dictionary.
547 */
548int
549ipc_addinfo( tr_benc         * list,
550             int               torrent_id,
551             tr_torrent      * tor,
552             int               types )
553{
554    tr_benc * dict;
555    int          ii, jj;
556    tr_file_index_t ff;
557    const tr_info * inf = tr_torrentInfo( tor );
558
559    /* always send torrent id */
560    types |= IPC_INF_ID;
561
562    tr_bencListReserve( list, 1 );
563
564    dict = tr_bencListAdd( list );
565
566    /* count the number of info keys and allocate a dict for them */
567    for( ii = jj = 0; IPC_INF__MAX > 1 << ii; ii++ )
568    {
569        if( !( types & ( 1 << ii ) ) )
570            continue;
571
572        assert( TR_N_ELEMENTS( gl_inf ) > ( unsigned )ii );
573        assert( gl_inf[ii].type == ( 1 << ii ) );
574        jj++;
575    }
576
577    tr_bencInitDict( dict, jj );
578
579    /* populate the dict with info key->value pairs */
580    for( ii = 0; IPC_INF__MAX > 1 << ii; ii++ )
581    {
582        tr_benc * item;
583
584        if( !( types & ( 1 << ii ) ) )
585            continue;
586
587        item = tr_bencDictAdd( dict, gl_inf[ii].name );
588        switch( 1 << ii )
589        {
590            case IPC_INF_COMMENT:
591                tr_bencInitStr( item, inf->comment ? inf->comment : "", -1, 1 );
592                break;
593            case IPC_INF_CREATOR:
594                tr_bencInitStr( item, inf->creator ? inf->creator : "", -1, 1 );
595                break;
596            case IPC_INF_DATE:
597                tr_bencInitInt( item, inf->dateCreated );
598                break;
599            case IPC_INF_FILES:
600                tr_bencInitList( item, inf->fileCount );
601                for( ff = 0; inf->fileCount > ff; ff++ )
602                {
603                    tr_benc * file = tr_bencListAdd( item );
604                    tr_bencInitDict( file, 2 );
605                    tr_bencInitStr( tr_bencDictAdd( file, "name" ),
606                                    inf->files[ff].name, -1, 1 );
607                    tr_bencInitInt( tr_bencDictAdd( file, "size" ),
608                                    inf->files[ff].length );
609                }
610                break;
611            case IPC_INF_HASH:
612                tr_bencInitStr( item, inf->hashString, -1, 1 );
613                break;
614            case IPC_INF_ID:
615                tr_bencInitInt( item, torrent_id );
616                break;
617            case IPC_INF_NAME:
618                tr_bencInitStr( item, inf->name, -1, 1 );
619                break;
620            case IPC_INF_PATH:
621                tr_bencInitStr( item, inf->torrent, -1, 1 );
622                break;
623            case IPC_INF_PRIVATE:
624                tr_bencInitInt( item, inf->isPrivate ? 1 : 0 );
625                break;
626            case IPC_INF_SIZE:
627                tr_bencInitInt( item, inf->totalSize );
628                break;
629            case IPC_INF_TRACKERS: {
630                int prevTier = -1;
631                tr_benc * tier = NULL;
632                tr_bencInitList( item, 0 );
633                for( jj=0; jj<inf->trackerCount; ++jj ) {
634                    if( prevTier != inf->trackers[jj].tier ) {
635                        prevTier = inf->trackers[jj].tier;
636                        tier = tr_bencListAddList( item, 0 );
637                    }
638                    filltracker( tr_bencListAdd( tier ), &inf->trackers[jj] );
639                }
640                break;
641            }
642            default:
643                assert( 0 );
644                break;
645        }
646    }
647
648    return 0;
649}
650
651/**
652 * append to "list" a dictionary whose keys are
653 * the string keys from gl_stat and whose values
654 * are torrent statistics set from "st".
655 *
656 * "types" is a bitwise-and'ed set of fields
657 * from the IPC_INF_* enum in ipcparse.h.
658 * It specifies what to put in the dictionary.
659 */
660int
661ipc_addstat( tr_benc      * list,
662             int            torrent_id,
663             tr_torrent   * tor,
664             int            types )
665{
666    const tr_stat * st = tr_torrentStatCached( tor );
667    tr_benc  * dict;
668    int ii, used;
669
670    /* add the dictionary child */
671    tr_bencListReserve( list, 1 );
672    dict = tr_bencListAdd( list );
673
674    /* always send torrent id */
675    types |= IPC_ST_ID;
676
677    /* count the number of stat keys and allocate a dict for them */
678    for( ii = used = 0; IPC_ST__MAX > 1 << ii; ii++ )
679        if( types & ( 1 << ii ) )
680            used++;
681    tr_bencInitDict( dict, used );
682
683    /* populate the dict */
684    for( ii = 0; IPC_ST__MAX > 1 << ii; ii++ )
685    {
686        tr_benc * item;
687
688        if( !( types & ( 1 << ii ) ) )
689            continue;
690
691        assert( TR_N_ELEMENTS( gl_stat ) > ( unsigned )ii );
692        assert( gl_stat[ii].type == ( 1 << ii ) );
693        item = tr_bencDictAdd( dict, gl_stat[ii].name );
694        switch( 1 << ii )
695        {
696            case IPC_ST_COMPLETED:
697            case IPC_ST_DOWNVALID:
698                tr_bencInitInt( item, st->haveValid );
699                break;
700            case IPC_ST_DOWNSPEED:
701                tr_bencInitInt( item, st->rateDownload * 1024 );
702                break;
703            case IPC_ST_DOWNTOTAL:
704                tr_bencInitInt( item, st->downloadedEver );
705                break;
706            case IPC_ST_ERROR: {
707                const tr_errno error = st->error;
708                if( TR_OK == error )
709                {
710                    tr_bencInitStr( item, "", -1, 1 );
711                }
712                else if( error == TR_ERROR_ASSERT )
713                {
714                    tr_bencInitStr( item, "assert", -1, 1 );
715                }
716                else if( error == TR_ERROR_IO_PERMISSIONS )
717                {
718                    tr_bencInitStr( item, "io-permissions", -1, 1 );
719                }
720                else if( error == TR_ERROR_IO_SPACE )
721                {
722                    tr_bencInitStr( item, "io-space", -1, 1 );
723                }
724                else if( error == TR_ERROR_IO_FILE_TOO_BIG )
725                {
726                    tr_bencInitStr( item, "io-file-too-big", -1, 1 );
727                }
728                else if( error == TR_ERROR_IO_OPEN_FILES )
729                {
730                    tr_bencInitStr( item, "io-open-files", -1, 1 );
731                }
732                else if( TR_ERROR_IS_IO( error ) )
733                {
734                    tr_bencInitStr( item, "io-other", -1, 1 );
735                }
736                else if( error == TR_ERROR_TC_ERROR )
737                {
738                    tr_bencInitStr( item, "tracker-error", -1, 1 );
739                }
740                else if( error == TR_ERROR_TC_WARNING )
741                {
742                    tr_bencInitStr( item, "tracker-warning", -1, 1 );
743                }
744                else if( TR_ERROR_IS_TC( error ) )
745                {
746                    tr_bencInitStr( item, "tracker-other", -1, 1 );
747                }
748                else
749                {
750                    tr_bencInitStr( item, "other", -1, 1 );
751                }
752                break;
753            }
754            case IPC_ST_ERRMSG:
755                if( TR_OK == st->error )
756                {
757                    tr_bencInitStr( item, "", -1, 1 );
758                }
759                else if( '\0' == st->errorString[0] )
760                {
761                    tr_bencInitStr( item, "other", -1, 1 );
762                }
763                else if( tr_bencInitStrDup( item, st->errorString ) )
764                {
765                    return -1;
766                }
767                break;
768            case IPC_ST_ETA:
769                tr_bencInitInt( item, st->eta );
770                break;
771            case IPC_ST_ID:
772                tr_bencInitInt( item, torrent_id );
773                break;
774            case IPC_ST_PEERDOWN:
775                tr_bencInitInt( item, st->peersSendingToUs );
776                break;
777            case IPC_ST_PEERFROM:
778                tr_bencInitDict( item, 4 );
779                tr_bencInitInt( tr_bencDictAdd( item, "incoming" ),
780                                st->peersFrom[TR_PEER_FROM_INCOMING] );
781                tr_bencInitInt( tr_bencDictAdd( item, "tracker" ),
782                                st->peersFrom[TR_PEER_FROM_TRACKER] );
783                tr_bencInitInt( tr_bencDictAdd( item, "cache" ),
784                                st->peersFrom[TR_PEER_FROM_CACHE] );
785                tr_bencInitInt( tr_bencDictAdd( item, "pex" ),
786                                st->peersFrom[TR_PEER_FROM_PEX] );
787                break;
788            case IPC_ST_PEERMAX:
789                tr_bencInitInt( item, tor->maxConnectedPeers );
790                break;
791            case IPC_ST_PEERTOTAL:
792                tr_bencInitInt( item, st->peersConnected );
793                break;
794            case IPC_ST_PEERUP:
795                tr_bencInitInt( item, st->peersGettingFromUs );
796                break;
797            case IPC_ST_RUNNING:
798                tr_bencInitInt( item, TR_STATUS_IS_ACTIVE(st->status) );
799                break;
800            case IPC_ST_STATE:
801                if( TR_STATUS_CHECK_WAIT & st->status )
802                {
803                    tr_bencInitStr( item, "waiting to checking", -1, 1 );
804                }
805                else if( TR_STATUS_CHECK & st->status )
806                {
807                    tr_bencInitStr( item, "checking", -1, 1 );
808                }
809                else if( TR_STATUS_DOWNLOAD & st->status )
810                {
811                    tr_bencInitStr( item, "downloading", -1, 1 );
812                }
813                else if( TR_STATUS_SEED & st->status )
814                {
815                    tr_bencInitStr( item, "seeding", -1, 1 );
816                }
817                else if( TR_STATUS_STOPPED & st->status )
818                {
819                    tr_bencInitStr( item, "paused", -1, 1 );
820                }
821                else
822                {
823                    assert( 0 );
824                }
825                break;
826            case IPC_ST_SWARM:
827                tr_bencInitInt( item, st->swarmspeed * 1024 );
828                break;
829            case IPC_ST_TRACKER:
830                filltracker( item, st->tracker );
831                break;
832            case IPC_ST_TKDONE:
833                tr_bencInitInt( item, st->completedFromTracker );
834                break;
835            case IPC_ST_TKLEECH:
836                tr_bencInitInt( item, st->leechers );
837                break;
838            case IPC_ST_TKSEED:
839                tr_bencInitInt( item, st->seeders );
840                break;
841            case IPC_ST_UPSPEED:
842                tr_bencInitInt( item, st->rateUpload * 1024 );
843                break;
844            case IPC_ST_UPTOTAL:
845                tr_bencInitInt( item, st->uploadedEver );
846                break;
847            default:
848                assert( 0 );
849                break;
850        }
851    }
852
853    return 0;
854}
855
856/**
857 * This reads a handshake message from the client to decide
858 * which IPC protocol version to use.
859 * Returns 0 on success; otherwise, returns -1 and sets errno.
860 *
861 * @see ipc_handleMessages()
862 * @see ipc_mkvers()
863 */
864static int
865handlevers( struct ipc_info * info, tr_benc * dict )
866{
867    tr_benc * vers;
868    int64_t      min, max;
869
870    if( !tr_bencIsDict( dict ) )
871    {
872        errno = EINVAL;
873        return -1;
874    }
875
876    vers = tr_bencDictFind( dict, MSGNAME( IPC_MSG_VERSION ) );
877    if( NULL == vers )
878    {
879        errno = EINVAL;
880        return -1;
881    }
882
883    switch( vers->type )
884    {
885        case TYPE_INT:
886            min = vers->val.i;
887            max = vers->val.i;
888            break;
889        case TYPE_DICT: {
890            tr_benc * num = tr_bencDictFind( vers, "min" );
891            min = tr_bencIsInt( num ) ? num->val.i : -1;
892            num = tr_bencDictFind( vers, "max" );
893            max = tr_bencIsInt( num ) ? num->val.i : -1;
894            break;
895        }
896        default:
897            min = -1;
898            max = -1;
899            break;
900    }
901
902    if( 0 >= min || 0 >= max || INT_MAX < min || INT_MAX < max )
903    {
904        errno = EINVAL;
905        return -1;
906    }
907
908    assert( PROTO_VERS_MIN <= PROTO_VERS_MAX );
909    if( min > max )
910    {
911        errno = EINVAL;
912        return -1;
913    }
914    if( PROTO_VERS_MAX < min || PROTO_VERS_MIN > max )
915    {
916        errno = EPERM;
917        return -1;
918    }
919
920    info->vers = MIN( PROTO_VERS_MAX, max );
921
922    return 0;
923}
924
925static int
926compareNameToMsg( const void * a, const void * b )
927{
928    const struct msg * msg = b;
929    return strcmp( a, msg->name );
930}
931
932static const struct msg *
933msglookup( const char * name )
934{
935    return bsearch( name,
936                    gl_msgs, TR_N_ELEMENTS( gl_msgs ), sizeof( struct msg ),
937                    compareNameToMsg );
938}
939
940enum ipc_msg
941ipc_msgid( const struct ipc_info * info, const char * name )
942{
943    const struct msg * msg = msglookup( name );
944
945    return msg && sessionSupportsMessage( info, msg->id )
946        ? msg->id
947        : IPC__MSG_COUNT;
948}
949
950/**
951 * Invokes the trd_msgfunc for the message passed in.
952 * Returns 0 on success; otherwise, returns -1 and sets errno.
953 */
954static int
955callmsgfunc( const struct ipc_info  * info,
956             tr_benc                * name,
957             tr_benc                * val,
958             tr_benc                * tagval,
959             void                   * user_data )
960{
961    const struct msg * msg;
962    int64_t            tag;
963
964    /* extract tag from tagval */
965    if( !tagval )
966        tag = -1;
967    else if( tr_bencIsInt( tagval ) )
968        tag = tagval->val.i;
969    else {
970        errno = EINVAL;
971        return -1;
972    }
973
974    /* find the msg corresponding to `name' */
975    if( !tr_bencIsString( name ) ) {
976        errno = EINVAL;
977        return -1;
978    }
979    msg = msglookup( name->val.s.s );
980
981    if( msg && msg->minvers <= info->vers )
982    {
983        if( info->funcs->msgs[msg->id] != NULL )
984        {
985            (*info->funcs->msgs[msg->id])( msg->id, val, tag, user_data );
986        }
987        else if( info->funcs->def )
988        {
989            info->funcs->def( msg->id, val, tag, user_data );
990        }
991    }
992    else if( NULL != info->funcs->def )
993        info->funcs->def( IPC__MSG_UNKNOWN, NULL, tag, user_data );
994
995    return 0;
996}
997
998static int
999handlemsgs( const struct ipc_info  * session,
1000            tr_benc                * message,
1001            void                   * user_data )
1002{
1003    tr_benc * name, * val, * tag;
1004
1005    assert( ipc_hasvers( session ) );
1006
1007    if( DICTPAYLOAD( session ) )
1008    {
1009        int ii;
1010
1011        if( TYPE_DICT != message->type || message->val.l.count % 2 )
1012        {
1013            errno = EINVAL;
1014            return -1;
1015        }
1016
1017        for( ii = 0; ii < message->val.l.count; ii += 2 )
1018        {
1019            assert( ii + 1 < message->val.l.count );
1020            name = &message->val.l.vals[ii];
1021            val  = &message->val.l.vals[ii+1];
1022            if( 0 > callmsgfunc( session, name, val, NULL, user_data ) )
1023                return -1;
1024        }
1025    }
1026    else
1027    {
1028        if( TYPE_LIST != message->type || 2 > message->val.l.count )
1029        {
1030            errno = EINVAL;
1031            return -1;
1032        }
1033
1034        name = &message->val.l.vals[0];
1035        val  = &message->val.l.vals[1];
1036        tag  = ( 2 == message->val.l.count ? NULL : &message->val.l.vals[2] );
1037        if( 0 > callmsgfunc( session, name, val, tag, user_data ) )
1038            return -1;
1039    }
1040
1041    return 0;
1042}
1043
1044ssize_t
1045ipc_handleMessages( struct ipc_info  * info,
1046                    const uint8_t    * msgs,
1047                    ssize_t            msgslen,
1048                    void             * user_data )
1049{
1050    char        hex[IPC_MIN_MSG_LEN+1], * end;
1051    ssize_t     off, len;
1052    tr_benc  benc;
1053
1054    for( off = 0; off + IPC_MIN_MSG_LEN < msgslen; off += IPC_MIN_MSG_LEN + len )
1055    {
1056        memcpy( hex, msgs + off, IPC_MIN_MSG_LEN );
1057        hex[IPC_MIN_MSG_LEN] = '\0';
1058        end = NULL;
1059        len = strtol( hex, &end, 16 );
1060        if( hex + IPC_MIN_MSG_LEN != end ||
1061            0 > len || IPC_MAX_MSG_LEN < len )
1062        {
1063            errno = EINVAL;
1064            return -1;
1065        }
1066        if( off + IPC_MIN_MSG_LEN + len > msgslen )
1067        {
1068            break;
1069        }
1070        errno = 0;
1071        if( tr_bencLoad( msgs + off + IPC_MIN_MSG_LEN, len, &benc, NULL ) )
1072        {
1073            if( 0 == errno )
1074            {
1075                errno = EINVAL;
1076            }
1077            return -1;
1078        }
1079        if( 0 > ( ipc_hasvers( info ) ? handlemsgs( info, &benc, user_data ) :
1080                                        handlevers( info, &benc ) ) )
1081        {
1082            SAFEBENCFREE( &benc );
1083            return -1;
1084        }
1085        tr_bencFree( &benc );
1086    }
1087
1088    return off;
1089}
1090
1091static int
1092compareNameToInf( const void * a, const void * b )
1093{
1094    const struct inf * inf = b;
1095    return strcmp( a, inf->name );
1096}
1097
1098/**
1099 * Convert a benc list of string keys from gl_inf or gl_stat
1100 * into a bitwise-or'ed int representation.
1101 * msg_id must be either IPC_MSG_INFO or IPC_MSG_STAT.
1102 * @see ipc_infoname()
1103 */
1104int
1105ipc_infotypes( enum ipc_msg id, const tr_benc * list )
1106{
1107    const struct inf     * array;
1108    size_t                 len;
1109    int                    i;
1110    int                    ret;
1111
1112    switch( id )
1113    {
1114        case IPC_MSG_INFO:
1115            array = gl_inf;
1116            len   = TR_N_ELEMENTS( gl_inf );
1117            break;
1118        case IPC_MSG_STAT:
1119            array = gl_stat;
1120            len   = TR_N_ELEMENTS( gl_stat );
1121            break;
1122        default:
1123            assert( 0 );
1124            break;
1125    }
1126
1127    ret = IPC_INF_ID;
1128
1129    if( !tr_bencIsList( list ) )
1130        return ret;
1131
1132    for( i=0; i<list->val.l.count; ++i )
1133    {
1134        const tr_benc * name = &list->val.l.vals[i];
1135        const struct inf * inf;
1136
1137        if( !tr_bencIsString( name ) )
1138            continue;
1139
1140        inf = bsearch( name->val.s.s,
1141                       array, len, sizeof( struct inf ),
1142                       compareNameToInf );
1143        if( inf )
1144            ret |= inf->type;
1145    }
1146
1147    return ret;
1148}
1149
1150/**
1151 * This function is the reverse of ipc_infotypes:
1152 * it returns the string key that corresponds to the type passed in.
1153 * Type is one of the IPC_INF_* or IPC_ST_* enums from ipcparse.h.
1154 * msg_id must be either IPC_MSG_INFO or IPC_MSG_STAT.
1155 * @see ipc_infotypes()
1156 */
1157const char *
1158ipc_infoname( enum ipc_msg id, int type )
1159{
1160    const struct inf * array;
1161    size_t len, ii;
1162
1163    switch( id )
1164    {
1165        case IPC_MSG_INFO:
1166            array = gl_inf;
1167            len   = TR_N_ELEMENTS( gl_inf );
1168            break;
1169        case IPC_MSG_STAT:
1170            array = gl_stat;
1171            len   = TR_N_ELEMENTS( gl_stat );
1172            break;
1173        default:
1174            assert( 0 );
1175            break;
1176    }
1177
1178    for( ii = 0; len > ii; ii++ )
1179        if( array[ii].type == type )
1180            return array[ii].name;
1181
1182    assert( 0 );
1183    return NULL;
1184}
Note: See TracBrowser for help on using the repository browser.