From 8a8c8711d889f53f549b936495db69b7f3b2e946 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 5 Apr 2009 17:52:21 +0000 Subject: [PATCH] (trunk) Use proper notation for json floating-point and bool types. For backwards compatability, still allow old-style printf strings as doubles, and 0s and 1s as bools. --- doc/rpc-spec.txt | 79 ++++++++--------- libtransmission/bencode.c | 176 ++++++++++++++++++++++++++++++-------- libtransmission/bencode.h | 30 +++++-- 3 files changed, 196 insertions(+), 89 deletions(-) diff --git a/doc/rpc-spec.txt b/doc/rpc-spec.txt index 40abdccbf..ad1728968 100644 --- a/doc/rpc-spec.txt +++ b/doc/rpc-spec.txt @@ -13,15 +13,6 @@ 2. Message Format - Messages are formatted in a subset of JSON easily represented - as bencoded data. Arrays, objects, strings, and whole numbers - all have one-to-one mappings between JSON and benc. - - Booleans and floating-point numbers are also used in the JSON messages. - Those two types aren't native to benc, so they're encoded this way: - Booleans are encoded as numbers where 0 is false and 1 is true. - Floating-point numbers are represented as strings. - Messages are formatted as objects. There are two types: requests (described in 2.1) and responses (described in 2.2). @@ -98,19 +89,19 @@ string | value type & description ----------------------------------+------------------------------------------------- "downloadLimit" | number maximum download speed (in K/s) - "downloadLimited" | 'boolean' true if "downloadLimit" is honored + "downloadLimited" | boolean true if "downloadLimit" is honored "files-wanted" | array indices of file(s) to download "files-unwanted" | array indices of file(s) to not download - "honorsSessionLimits" | 'boolean' true if session upload limits are honored + "honorsSessionLimits" | boolean true if session upload limits are honored "ids" | array torrent list, as described in 3.1 "peer-limit" | number maximum number of peers "priority-high" | array indices of high-priority file(s) "priority-low" | array indices of low-priority file(s) "priority-normal" | array indices of normal-priority file(s) - "seedRatioLimit" | 'double' session seeding ratio + "seedRatioLimit" | double session seeding ratio "seedRatioMode" | number which ratio to use. See tr_ratiolimit "uploadLimit" | number maximum upload speed (in K/s) - "uploadLimited" | 'boolean' true if "uploadLimit" is honored + "uploadLimited" | boolean true if "uploadLimit" is honored Just as an empty "ids" value is shorthand for "all ids", using an empty array for "files-wanted", "files-unwanted", "priority-high", "priority-low", or @@ -151,7 +142,7 @@ downloadedEver | number | tr_stat downloaders | number | tr_stat downloadLimit | number | tr_torrent - downloadLimited | 'boolean' | tr_torrent + downloadLimited | boolean | tr_torrent error | number | tr_stat errorString | number | tr_stat eta | number | tr_stat @@ -160,9 +151,9 @@ hashString | string | tr_info haveUnchecked | number | tr_stat haveValid | number | tr_stat - honorsSessionLimits | 'boolean' | tr_torrent + honorsSessionLimits | boolean | tr_torrent id | number | tr_torrent - isPrivate | 'boolean' | tr_torrent + isPrivate | boolean | tr_torrent lastAnnounceTime | number | tr_stat lastScrapeTime | number | tr_stat leechers | number | tr_stat @@ -179,19 +170,19 @@ peersGettingFromUs | number | tr_stat peersKnown | number | tr_stat peersSendingToUs | number | tr_stat - percentDone | 'double' | tr_stat + percentDone | double | tr_stat pieces | string (see below) | tr_torrent pieceCount | tnumber | tr_info pieceSize | tnumber | tr_info priorities | array (see below) | n/a - ratio | 'double' | tr_stat + ratio | double | tr_stat rateDownload (B/s) | number | tr_stat rateUpload (B/s) | number | tr_stat - recheckProgress | 'double' | tr_stat + recheckProgress | double | tr_stat scrapeResponse | string | tr_stat scrapeURL | string | tr_stat seeders | number | tr_stat - seedRatioLimit | 'double' | tr_torrent + seedRatioLimit | double | tr_torrent seedRatioMode | number | tr_ratiolimit sizeWhenDone | number | tr_stat startDate | number | tr_stat @@ -203,8 +194,8 @@ torrentFile | string | tr_info uploadedEver | number | tr_stat uploadLimit | number | tr_torrent - uploadLimited | 'boolean' | tr_torrent - uploadRatio | 'double' | tr_stat + uploadLimited | boolean | tr_torrent + uploadRatio | double | tr_stat wanted | array (see below) | n/a webseeds | array (see below) | n/a webseedsSendingToUs | number | tr_stat @@ -223,23 +214,23 @@ | each containing: | +-------------------------+------------+ | bytesCompleted | number | tr_torrent - | wanted | 'boolean' | tr_info + | wanted | boolean | tr_info | priority | number | tr_info -----------------------+--------------------------------------+ peers | array of objects, each containing: | +-------------------------+------------+ | address | string | tr_peer_stat | clientName | string | tr_peer_stat - | clientIsChoked | 'boolean' | tr_peer_stat - | clientIsInterested | 'boolean' | tr_peer_stat - | isDownloadingFrom | 'boolean' | tr_peer_stat - | isEncrypted | 'boolean' | tr_peer_stat - | isIncoming | 'boolean' | tr_peer_stat - | isUploadingTo | 'boolean' | tr_peer_stat - | peerIsChoked | 'boolean' | tr_peer_stat - | peerIsInterested | 'boolean' | tr_peer_stat + | clientIsChoked | boolean | tr_peer_stat + | clientIsInterested | boolean | tr_peer_stat + | isDownloadingFrom | boolean | tr_peer_stat + | isEncrypted | boolean | tr_peer_stat + | isIncoming | boolean | tr_peer_stat + | isUploadingTo | boolean | tr_peer_stat + | peerIsChoked | boolean | tr_peer_stat + | peerIsInterested | boolean | tr_peer_stat | port | number | tr_peer_stat - | progress | 'double' | tr_peer_stat + | progress | double | tr_peer_stat | rateToClient (B/s) | number | tr_peer_stat | rateToPeer (B/s) | number | tr_peer_stat -----------------------+--------------------------------------+ @@ -323,7 +314,7 @@ "download-dir" | string path to download the torrent to "filename" | string filename or URL of the .torrent file "metainfo" | string base64-encoded .torrent content - "paused" | 'boolean' if true, don't start the torrent + "paused" | boolean if true, don't start the torrent "peer-limit" | number maximum number of peers "files-wanted" | array indices of file(s) to download "files-unwanted" | array indices of file(s) to not download @@ -347,7 +338,7 @@ string | value type & description ---------------------------+------------------------------------------------- "ids" | array torrent list, as described in 3.1 - "delete-local-data" | 'boolean' delete local data. (default: false) + "delete-local-data" | boolean delete local data. (default: false) Response arguments: none @@ -359,30 +350,30 @@ string | value type & description ---------------------------+------------------------------------------------- "alt-speed-down" | number max global download speed (in K/s) - "alt-speed-enabled" | 'boolean' true means use the alt speeds + "alt-speed-enabled" | boolean true means use the alt speeds "alt-speed-time-begin" | number when to turn on alt speeds (units: minutes after midnight) - "alt-speed-time-enabled" | 'boolean' true means the scheduled on/off times are used + "alt-speed-time-enabled" | boolean true means the scheduled on/off times are used "alt-speed-time-end" | number when to turn off alt speeds (units: same) "alt-speed-time-day" | number what day(s) to turn on alt speeds (look at tr_sched_day) "alt-speed-up" | number max global upload speed (in K/s) - "blocklist-enabled" | 'boolean' true means enabled + "blocklist-enabled" | boolean true means enabled "blocklist-size" | number number of rules in the blocklist "encryption" | string "required", "preferred", "tolerated" "download-dir" | string default path to download torrents "peer-limit-global" | number maximum global number of peers "peer-limit-per-torrent" | number maximum global number of peers - "pex-enabled" | 'boolean' true means allow pex in public torrents + "pex-enabled" | boolean true means allow pex in public torrents "peer-port" | number port number - "peer-port-random-on-start"| 'boolean' true means pick a random peer port on launch - "port-forwarding-enabled" | 'boolean' true means enabled + "peer-port-random-on-start"| boolean true means pick a random peer port on launch + "port-forwarding-enabled" | boolean true means enabled "rpc-version" | number the current RPC API version "rpc-version-minimum" | number the minimum RPC API version supported - "seedRatioLimit" | 'double' the default seed ratio for torrents to use - "seedRatioLimited" | 'boolean' true if seedRatioLimit is honored by default + "seedRatioLimit" | double the default seed ratio for torrents to use + "seedRatioLimited" | boolean true if seedRatioLimit is honored by default "speed-limit-down" | number max global download speed (in K/s) - "speed-limit-down-enabled" | 'boolean' true means enabled + "speed-limit-down-enabled" | boolean true means enabled "speed-limit-up" | number max global upload speed (in K/s) - "speed-limit-up-enabled" | 'boolean' true means enabled + "speed-limit-up-enabled" | boolean true means enabled "version" | string long version string "$version ($revision)" "rpc-version" indicates the RPC interface version supported by the RPC server. diff --git a/libtransmission/bencode.c b/libtransmission/bencode.c index c4571a542..902d12b5e 100644 --- a/libtransmission/bencode.c +++ b/libtransmission/bencode.c @@ -414,10 +414,17 @@ tr_bool tr_bencGetInt( const tr_benc * val, int64_t * setme ) { - const tr_bool success = tr_bencIsInt( val ); + tr_bool success = FALSE; - if( success && setme ) - *setme = val->val.i; + if( !success && (( success = tr_bencIsInt( val )))) + if( setme ) + *setme = val->val.i; + + if( !success && (( success = tr_bencIsBool( val )))) { + fprintf( stderr, "warning: reading bool as an int\n" ); + if( setme ) + *setme = val->val.b ? 1 : 0; + } return success; } @@ -439,6 +446,9 @@ tr_bencGetBool( const tr_benc * val, tr_bool * setme ) { tr_bool success = FALSE; + if(( success = tr_bencIsBool( val ))) + *setme = val->val.b; + if( !success && tr_bencIsInt( val ) ) if(( success = ( val->val.i==0 || val->val.i==1 ) )) *setme = val->val.i!=0; @@ -455,6 +465,12 @@ tr_bencGetReal( const tr_benc * val, double * setme ) { tr_bool success = FALSE; + if( !success && (( success = tr_bencIsReal( val )))) + *setme = val->val.d; + + if( !success && (( success = tr_bencIsInt( val )))) + *setme = val->val.i; + if( !success && tr_bencIsString(val) ) { char * endptr; @@ -472,11 +488,6 @@ tr_bencGetReal( const tr_benc * val, double * setme ) *setme = d; } - if( !success && tr_bencIsInt(val) ) - { - success = TRUE; - *setme = val->val.i; - } return success; } @@ -598,43 +609,52 @@ tr_bencInitStr( tr_benc * val, } void -tr_bencInitInt( tr_benc * val, - int64_t num ) +tr_bencInitBool( tr_benc * b, int value ) +{ + tr_bencInit( b, TYPE_BOOL ); + b->val.b = value != 0; +} + +void +tr_bencInitReal( tr_benc * b, double value ) { - tr_bencInit( val, TYPE_INT ); - val->val.i = num; + tr_bencInit( b, TYPE_REAL ); + b->val.d = value; +} + +void +tr_bencInitInt( tr_benc * b, int64_t value ) +{ + tr_bencInit( b, TYPE_INT ); + b->val.i = value; } int -tr_bencInitList( tr_benc * val, - size_t reserveCount ) +tr_bencInitList( tr_benc * b, size_t reserveCount ) { - tr_bencInit( val, TYPE_LIST ); - return tr_bencListReserve( val, reserveCount ); + tr_bencInit( b, TYPE_LIST ); + return tr_bencListReserve( b, reserveCount ); } int -tr_bencListReserve( tr_benc * val, - size_t count ) +tr_bencListReserve( tr_benc * b, size_t count ) { - assert( tr_bencIsList( val ) ); - return makeroom( val, count ); + assert( tr_bencIsList( b ) ); + return makeroom( b, count ); } int -tr_bencInitDict( tr_benc * val, - size_t reserveCount ) +tr_bencInitDict( tr_benc * b, size_t reserveCount ) { - tr_bencInit( val, TYPE_DICT ); - return tr_bencDictReserve( val, reserveCount ); + tr_bencInit( b, TYPE_DICT ); + return tr_bencDictReserve( b, reserveCount ); } int -tr_bencDictReserve( tr_benc * val, - size_t reserveCount ) +tr_bencDictReserve( tr_benc * b, size_t reserveCount ) { - assert( tr_bencIsDict( val ) ); - return makeroom( val, reserveCount * 2 ); + assert( tr_bencIsDict( b ) ); + return makeroom( b, reserveCount * 2 ); } tr_benc * @@ -716,16 +736,14 @@ tr_bencDictAdd( tr_benc * dict, return itemval; } -tr_benc* -tr_bencDictAddInt( tr_benc * dict, - const char * key, - int64_t val ) +static tr_benc* +dictFindOrAdd( tr_benc * dict, const char * key, int type ) { tr_benc * child; /* see if it already exists, and if so, try to reuse it */ if(( child = tr_bencDictFind( dict, key ))) { - if( !tr_bencIsInt( child ) ) { + if( !tr_bencIsType( child, type ) ) { tr_bencDictRemove( dict, key ); child = NULL; } @@ -735,18 +753,33 @@ tr_bencDictAddInt( tr_benc * dict, if( child == NULL ) child = tr_bencDictAdd( dict, key ); - /* set it */ - tr_bencInitInt( child, val ); + return child; +} +tr_benc* +tr_bencDictAddInt( tr_benc * dict, + const char * key, + int64_t val ) +{ + tr_benc * child = dictFindOrAdd( dict, key, TYPE_INT ); + tr_bencInitInt( child, val ); return child; } tr_benc* tr_bencDictAddBool( tr_benc * dict, const char * key, tr_bool val ) { - assert( tr_isBool( val ) ); + tr_benc * child = dictFindOrAdd( dict, key, TYPE_BOOL ); + tr_bencInitBool( child, val ); + return child; +} - return tr_bencDictAddInt( dict, key, val ); +tr_benc* +tr_bencDictAddReal( tr_benc * dict, const char * key, double val ) +{ + tr_benc * child = dictFindOrAdd( dict, key, TYPE_REAL ); + tr_bencInitReal( child, val ); + return child; } tr_benc* @@ -774,9 +807,11 @@ tr_bencDictAddStr( tr_benc * dict, const char * key, const char * val ) return child; } +#if 0 tr_benc* tr_bencDictAddReal( tr_benc * dict, const char * key, double d ) { + ccc char buf[128]; char * locale; @@ -789,6 +824,7 @@ tr_bencDictAddReal( tr_benc * dict, const char * key, double d ) return tr_bencDictAddStr( dict, key, buf ); } +#endif tr_benc* tr_bencDictAddList( tr_benc * dict, @@ -960,6 +996,8 @@ typedef void ( *BencWalkFunc )( const tr_benc * val, void * user_data ); struct WalkFuncs { BencWalkFunc intFunc; + BencWalkFunc boolFunc; + BencWalkFunc realFunc; BencWalkFunc stringFunc; BencWalkFunc dictBeginFunc; BencWalkFunc listBeginFunc; @@ -1011,6 +1049,14 @@ bencWalk( const tr_benc * top, walkFuncs->intFunc( val, user_data ); break; + case TYPE_BOOL: + walkFuncs->boolFunc( val, user_data ); + break; + + case TYPE_REAL: + walkFuncs->realFunc( val, user_data ); + break; + case TYPE_STR: walkFuncs->stringFunc( val, user_data ); break; @@ -1050,6 +1096,31 @@ saveIntFunc( const tr_benc * val, evbuffer_add_printf( evbuf, "i%" PRId64 "e", val->val.i ); } +static void +saveBoolFunc( const tr_benc * val, void * evbuf ) +{ + evbuffer_add_printf( evbuf, "i%de", val->val.b?1:0 ); +} + +static void +saveRealFunc( const tr_benc * val, void * evbuf ) +{ + char buf[128]; + char * locale; + size_t len; + + /* always use a '.' decimal point s.t. locale-hopping doesn't bite us */ + locale = tr_strdup( setlocale ( LC_NUMERIC, NULL ) ); + setlocale( LC_NUMERIC, "POSIX" ); + tr_snprintf( buf, sizeof( buf ), "%f", val->val.d ); + setlocale( LC_NUMERIC, locale ); + tr_free( locale ); + + len = strlen( buf ); + evbuffer_add_printf( evbuf, "%lu:", (unsigned long)buf ); + evbuffer_add( evbuf, buf, len ); +} + static void saveStringFunc( const tr_benc * val, void * vevbuf ) @@ -1090,6 +1161,8 @@ tr_bencSave( const tr_benc * top, struct evbuffer * out = tr_getBuffer( ); walkFuncs.intFunc = saveIntFunc; + walkFuncs.boolFunc = saveBoolFunc; + walkFuncs.realFunc = saveRealFunc; walkFuncs.stringFunc = saveStringFunc; walkFuncs.dictBeginFunc = saveDictBeginFunc; walkFuncs.listBeginFunc = saveListBeginFunc; @@ -1136,6 +1209,8 @@ tr_bencFree( tr_benc * val ) struct WalkFuncs walkFuncs; walkFuncs.intFunc = freeDummyFunc; + walkFuncs.boolFunc = freeDummyFunc; + walkFuncs.realFunc = freeDummyFunc; walkFuncs.stringFunc = freeStringFunc; walkFuncs.dictBeginFunc = freeContainerBeginFunc; walkFuncs.listBeginFunc = freeContainerBeginFunc; @@ -1235,6 +1310,31 @@ jsonIntFunc( const tr_benc * val, jsonChildFunc( data ); } +static void +jsonBoolFunc( const tr_benc * val, void * vdata ) +{ + struct jsonWalk * data = vdata; + + evbuffer_add_printf( data->out, "%s", (val->val.b?"true":"false") ); + jsonChildFunc( data ); +} + +static void +jsonRealFunc( const tr_benc * val, void * vdata ) +{ + struct jsonWalk * data = vdata; + char * locale; + + /* json requires a '.' decimal point regardless of locale */ + locale = tr_strdup( setlocale ( LC_NUMERIC, NULL ) ); + setlocale( LC_NUMERIC, "POSIX" ); + evbuffer_add_printf( data->out, "%f", val->val.d ); + setlocale( LC_NUMERIC, locale ); + tr_free( locale ); + + jsonChildFunc( data ); +} + static void jsonStringFunc( const tr_benc * val, void * vdata ) @@ -1366,6 +1466,8 @@ tr_bencSaveAsJSON( const tr_benc * top, struct evbuffer * out ) data.parents = NULL; walkFuncs.intFunc = jsonIntFunc; + walkFuncs.boolFunc = jsonBoolFunc; + walkFuncs.realFunc = jsonRealFunc; walkFuncs.stringFunc = jsonStringFunc; walkFuncs.dictBeginFunc = jsonDictBeginFunc; walkFuncs.listBeginFunc = jsonListBeginFunc; diff --git a/libtransmission/bencode.h b/libtransmission/bencode.h index 2e62e8823..2ad6a85b7 100644 --- a/libtransmission/bencode.h +++ b/libtransmission/bencode.h @@ -13,6 +13,10 @@ #ifndef TR_BENCODE_H #define TR_BENCODE_H 1 +#ifdef __cplusplus +extern "C" { +#endif + #include /* for int64_t */ struct evbuffer; @@ -22,7 +26,9 @@ enum TYPE_INT = 1, TYPE_STR = 2, TYPE_LIST = 4, - TYPE_DICT = 8 + TYPE_DICT = 8, + TYPE_BOOL = 16, + TYPE_REAL = 32 }; typedef struct tr_benc @@ -30,13 +36,19 @@ typedef struct tr_benc char type; union { - int64_t i; - struct + uint8_t b; /* bool type */ + + double d; /* double type */ + + int64_t i; /* int type */ + + struct /* string type */ { size_t i; char * s; } s; - struct + + struct /* list & dict types */ { size_t alloc; size_t count; @@ -45,10 +57,6 @@ typedef struct tr_benc } val; } tr_benc; -#ifdef __cplusplus -extern "C" { -#endif - /*** **** ***/ @@ -96,6 +104,10 @@ int tr_bencInitDict( tr_benc *, size_t reserveCount ); int tr_bencInitList( tr_benc *, size_t reserveCount ); +void tr_bencInitBool( tr_benc *, int value ); + +void tr_bencInitReal( tr_benc *, double value ); + /*** **** ***/ @@ -172,6 +184,8 @@ static TR_INLINE tr_bool tr_bencIsInt ( const tr_benc * b ) { return tr_bencIs static TR_INLINE tr_bool tr_bencIsDict ( const tr_benc * b ) { return tr_bencIsType( b, TYPE_DICT ); } static TR_INLINE tr_bool tr_bencIsList ( const tr_benc * b ) { return tr_bencIsType( b, TYPE_LIST ); } static TR_INLINE tr_bool tr_bencIsString( const tr_benc * b ) { return tr_bencIsType( b, TYPE_STR ); } +static TR_INLINE tr_bool tr_bencIsBool ( const tr_benc * b ) { return tr_bencIsType( b, TYPE_BOOL ); } +static TR_INLINE tr_bool tr_bencIsReal ( const tr_benc * b ) { return tr_bencIsType( b, TYPE_REAL ); } /** *** Treat these as private -- they're only made public here -- 2.40.0