source: trunk/libtransmission/bencode.c @ 5127

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

more housekeeping: benc_val_t --> tr_benc

  • Property svn:keywords set to Date Rev Author Id
File size: 20.5 KB
Line 
1/******************************************************************************
2 * $Id: bencode.c 5127 2008-02-26 21:58:58Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
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 <ctype.h> /* isdigit, isprint */
27#include <errno.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31
32#include <event.h> /* evbuffer */
33
34#include "transmission.h"
35#include "bencode.h"
36#include "ptrarray.h"
37#include "utils.h" /* tr_new(), tr_free() */
38
39/**
40***
41**/
42
43static int
44isType( const tr_benc * val, int type )
45{
46    return ( ( val != NULL ) && ( val->type == type ) );
47}
48
49#define isInt(v)    ( isType( ( v ), TYPE_INT ) )
50#define isString(v) ( isType( ( v ), TYPE_STR ) )
51#define isList(v)   ( isType( ( v ), TYPE_LIST ) )
52#define isDict(v)   ( isType( ( v ), TYPE_DICT ) )
53
54static int
55isContainer( const tr_benc * val )
56{
57    return isList(val) || isDict(val);
58}
59static int
60isSomething( const tr_benc * val )
61{
62    return isContainer(val) || isInt(val) || isString(val);
63}
64
65/***
66****  tr_bencParse()
67****  tr_bencLoad()
68***/
69
70/**
71 * The initial i and trailing e are beginning and ending delimiters.
72 * You can have negative numbers such as i-3e. You cannot prefix the
73 * number with a zero such as i04e. However, i0e is valid. 
74 * Example: i3e represents the integer "3"
75 * NOTE: The maximum number of bit of this integer is unspecified,
76 * but to handle it as a signed 64bit integer is mandatory to handle
77 * "large files" aka .torrent for more that 4Gbyte
78 */
79int
80tr_bencParseInt( const uint8_t  * buf,
81                 const uint8_t  * bufend,
82                 const uint8_t ** setme_end, 
83                 int64_t        * setme_val )
84{
85    int err = TR_OK;
86    char * endptr;
87    const void * begin;
88    const void * end;
89    int64_t val;
90
91    if( buf >= bufend )
92        return TR_ERROR;
93    if( *buf != 'i' )
94        return TR_ERROR;
95
96    begin = buf + 1;
97    end = memchr( begin, 'e', (bufend-buf)-1 );
98    if( end == NULL )
99        return TR_ERROR;
100
101    errno = 0;
102    val = strtoll( begin, &endptr, 10 );
103    if( errno || ( endptr != end ) ) /* incomplete parse */
104        err = TR_ERROR;
105    else if( val && *(const char*)begin=='0' ) /* no leading zeroes! */
106        err = TR_ERROR;
107    else {
108        *setme_end = end + 1;
109        *setme_val = val;
110    }
111
112    return err;
113}
114
115
116/**
117 * Byte strings are encoded as follows:
118 * <string length encoded in base ten ASCII>:<string data>
119 * Note that there is no constant beginning delimiter, and no ending delimiter.
120 * Example: 4:spam represents the string "spam"
121 */
122int
123tr_bencParseStr( const uint8_t  * buf,
124                 const uint8_t  * bufend,
125                 const uint8_t ** setme_end,
126                 uint8_t       ** setme_str,
127                 size_t         * setme_strlen )
128{
129    size_t len;
130    const void * end;
131    char * endptr;
132
133    if( buf >= bufend )
134        return TR_ERROR;
135
136    if( !isdigit( *buf  ) )
137        return TR_ERROR;
138
139    end = memchr( buf, ':', bufend-buf );
140    if( end == NULL )
141        return TR_ERROR;
142
143    errno = 0;
144    len = strtoul( (const char*)buf, &endptr, 10 );
145    if( errno || endptr!=end )
146        return TR_ERROR;
147
148    if( (const uint8_t*)end + 1 + len > bufend )
149        return TR_ERROR;
150
151    *setme_end = end + 1 + len;
152    *setme_str = (uint8_t*) tr_strndup( end + 1, len );
153    *setme_strlen = len;
154    return TR_OK;
155}
156
157/* setting to 1 to help expose bugs with tr_bencListAdd and tr_bencDictAdd */
158#define LIST_SIZE 8 /* number of items to increment list/dict buffer by */
159
160static int
161makeroom( tr_benc * val, int count )
162{
163    assert( TYPE_LIST == val->type || TYPE_DICT == val->type );
164
165    if( val->val.l.count + count > val->val.l.alloc )
166    {
167        /* We need a bigger boat */
168        const int len = val->val.l.alloc + count +
169            ( count % LIST_SIZE ? LIST_SIZE - ( count % LIST_SIZE ) : 0 );
170        void * new = realloc( val->val.l.vals, len * sizeof( tr_benc ) );
171        if( NULL == new )
172            return 1;
173
174        val->val.l.alloc = len;
175        val->val.l.vals  = new;
176    }
177
178    return 0;
179}
180
181static tr_benc*
182getNode( tr_benc * top, tr_ptrArray * parentStack, int type )
183{
184    tr_benc * parent;
185
186    assert( top != NULL );
187    assert( parentStack != NULL );
188
189    if( tr_ptrArrayEmpty( parentStack ) )
190        return top;
191
192    parent = tr_ptrArrayBack( parentStack );
193    assert( parent != NULL );
194
195    /* dictionary keys must be strings */
196    if( ( parent->type == TYPE_DICT )
197        && ( type != TYPE_STR )
198        && ( ! ( parent->val.l.count % 2 ) ) )
199        return NULL;
200
201    makeroom( parent, 1 );
202    return parent->val.l.vals + parent->val.l.count++;
203}
204
205/**
206 * This function's previous recursive implementation was
207 * easier to read, but was vulnerable to a smash-stacking
208 * attack via maliciously-crafted bencoded data. (#667)
209 */
210int
211tr_bencParse( const void     * buf_in,
212              const void     * bufend_in,
213              tr_benc     * top,
214              const uint8_t ** setme_end )
215{
216    int err;
217    const uint8_t * buf = buf_in;
218    const uint8_t * bufend = bufend_in;
219    tr_ptrArray * parentStack = tr_ptrArrayNew( );
220
221    tr_bencInit( top, 0 );
222
223    while( buf != bufend )
224    {
225        if( buf > bufend ) /* no more text to parse... */
226            return 1;
227
228        if( *buf=='i' ) /* int */
229        {
230            int64_t val;
231            const uint8_t * end;
232            int err;
233            tr_benc * node;
234
235            if(( err = tr_bencParseInt( buf, bufend, &end, &val )))
236                return err;
237
238            node = getNode( top, parentStack, TYPE_INT );
239            if( !node )
240                return TR_ERROR;
241
242            tr_bencInitInt( node, val );
243            buf = end;
244
245            if( tr_ptrArrayEmpty( parentStack ) )
246                break;
247        }
248        else if( *buf=='l' ) /* list */
249        {
250            tr_benc * node = getNode( top, parentStack, TYPE_LIST );
251            if( !node )
252                return TR_ERROR;
253            tr_bencInit( node, TYPE_LIST );
254            tr_ptrArrayAppend( parentStack, node );
255            ++buf;
256        }
257        else if( *buf=='d' ) /* dict */
258        {
259            tr_benc * node = getNode( top, parentStack, TYPE_DICT );
260            if( !node )
261                return TR_ERROR;
262            tr_bencInit( node, TYPE_DICT );
263            tr_ptrArrayAppend( parentStack, node );
264            ++buf;
265        }
266        else if( *buf=='e' ) /* end of list or dict */
267        {
268            tr_benc * node;
269            ++buf;
270            if( tr_ptrArrayEmpty( parentStack ) )
271                return TR_ERROR;
272
273            node = tr_ptrArrayBack( parentStack );
274            if( isDict( node ) && ( node->val.l.count % 2 ) )
275                return TR_ERROR; /* odd # of children in dict */
276
277            tr_ptrArrayPop( parentStack );
278            if( tr_ptrArrayEmpty( parentStack ) )
279                break;
280        }
281        else if( isdigit(*buf) ) /* string? */
282        {
283            const uint8_t * end;
284            uint8_t * str;
285            size_t str_len;
286            int err;
287            tr_benc * node;
288
289            if(( err = tr_bencParseStr( buf, bufend, &end, &str, &str_len )))
290                return err;
291
292            node = getNode( top, parentStack, TYPE_STR );
293            if( !node )
294                return TR_ERROR;
295
296            tr_bencInitStr( node, str, str_len, 0 );
297            buf = end;
298
299            if( tr_ptrArrayEmpty( parentStack ) )
300                break;
301        }
302        else /* invalid bencoded text... march past it */
303        {
304            ++buf;
305        }
306    }
307
308    err = !isSomething( top ) || !tr_ptrArrayEmpty( parentStack );
309
310    if( !err && ( setme_end != NULL ) )
311        *setme_end = buf;
312
313    tr_ptrArrayFree( parentStack, NULL );
314    return err;
315}
316
317int
318tr_bencLoad( const void  * buf_in,
319             int           buflen,
320             tr_benc  * setme_benc,
321             char       ** setme_end )
322{
323    const uint8_t * buf = buf_in;
324    const uint8_t * end;
325    const int ret = tr_bencParse( buf, buf+buflen, setme_benc, &end );
326    if( !ret && setme_end )
327        *setme_end = (char*) end;
328    return ret;
329}
330
331/***
332****
333***/
334
335tr_benc *
336tr_bencDictFind( tr_benc * val, const char * key )
337{
338    int len, ii;
339
340    if( !isDict( val ) )
341        return NULL;
342
343    len = strlen( key );
344   
345    for( ii = 0; ii + 1 < val->val.l.count; ii += 2 )
346    {
347        if( TYPE_STR  != val->val.l.vals[ii].type ||
348            len       != val->val.l.vals[ii].val.s.i ||
349            0 != memcmp( val->val.l.vals[ii].val.s.s, key, len ) )
350        {
351            continue;
352        }
353        return &val->val.l.vals[ii+1];
354    }
355
356    return NULL;
357}
358
359tr_benc*
360tr_bencDictFindType( tr_benc * val, const char * key, int type )
361{
362    tr_benc * ret = tr_bencDictFind( val, key );
363    return ret && ret->type == type ? ret : NULL;
364}
365
366tr_benc *
367tr_bencDictFindFirst( tr_benc * val, ... )
368{
369    const char * key;
370    tr_benc * ret;
371    va_list      ap;
372
373    ret = NULL;
374    va_start( ap, val );
375    while(( key = va_arg( ap, const char * )))
376        if(( ret = tr_bencDictFind( val, key )))
377            break;
378    va_end( ap );
379
380    return ret;
381}
382
383tr_benc*
384tr_bencListGetNthChild( tr_benc * val, int i )
385{
386    tr_benc * ret = NULL;
387    if( isList( val ) && ( i >= 0 ) && ( i < val->val.l.count ) )
388        ret = val->val.l.vals + i;
389    return ret;
390}
391
392int64_t
393tr_bencGetInt ( const tr_benc * val )
394{
395    assert( isInt( val ) );
396    return val->val.i;
397}
398
399char *
400tr_bencStealStr( tr_benc * val )
401{
402    assert( isString( val ) );
403    val->val.s.nofree = 1;
404    return val->val.s.s;
405}
406
407/***
408****
409***/
410
411void
412_tr_bencInitStr( tr_benc * val, char * str, int len, int nofree )
413{
414    tr_bencInit( val, TYPE_STR );
415    val->val.s.s      = str;
416    val->val.s.nofree = nofree;
417    if( 0 >= len )
418    {
419        len = ( NULL == str ? 0 : strlen( str ) );
420    }
421    val->val.s.i = len;
422}
423
424int
425tr_bencInitStrDup( tr_benc * val, const char * str )
426{
427    char * newStr = tr_strdup( str );
428    if( newStr == NULL )
429        return 1;
430
431    _tr_bencInitStr( val, newStr, 0, 0 );
432    return 0;
433}
434
435void
436tr_bencInitInt( tr_benc * val, int64_t num )
437{
438    tr_bencInit( val, TYPE_INT );
439    val->val.i = num;
440}
441
442int
443tr_bencListReserve( tr_benc * val, int count )
444{
445    assert( isList( val ) );
446
447    return makeroom( val, count );
448}
449
450int
451tr_bencDictReserve( tr_benc * val, int count )
452{
453    assert( isDict( val ) );
454
455    return makeroom( val, count * 2 );
456}
457
458tr_benc *
459tr_bencListAdd( tr_benc * list )
460{
461    tr_benc * item;
462
463    assert( isList( list ) );
464    assert( list->val.l.count < list->val.l.alloc );
465
466    item = &list->val.l.vals[list->val.l.count];
467    list->val.l.count++;
468    tr_bencInit( item, TYPE_INT );
469
470    return item;
471}
472
473tr_benc *
474tr_bencDictAdd( tr_benc * dict, const char * key )
475{
476    tr_benc * keyval, * itemval;
477
478    assert( isDict( dict ) );
479    assert( dict->val.l.count + 2 <= dict->val.l.alloc );
480
481    keyval = dict->val.l.vals + dict->val.l.count++;
482    tr_bencInitStr( keyval, (char*)key, -1, 1 );
483
484    itemval = dict->val.l.vals + dict->val.l.count++;
485    tr_bencInit( itemval, TYPE_INT );
486
487    return itemval;
488}
489
490/***
491****  BENC WALKING
492***/
493
494struct KeyIndex
495{
496    const char * key;
497    int index;
498};
499
500static int
501compareKeyIndex( const void * va, const void * vb )
502{
503    const struct KeyIndex * a = va;
504    const struct KeyIndex * b = vb;
505    return strcmp( a->key, b->key );
506}
507
508struct SaveNode
509{
510    const tr_benc * val;
511    int valIsVisited;
512    int childCount;
513    int childIndex;
514    int * children;
515};
516
517static struct SaveNode*
518nodeNewDict( const tr_benc * val )
519{
520    int i, j;
521    int nKeys;
522    struct SaveNode * node;
523    struct KeyIndex * indices;
524
525    assert( isDict( val ) );
526
527    nKeys = val->val.l.count / 2;
528    node = tr_new0( struct SaveNode, 1 );
529    node->val = val;
530    node->children = tr_new0( int, nKeys * 2 );
531
532    /* ugh, a dictionary's children have to be sorted by key... */
533    indices = tr_new( struct KeyIndex, nKeys );
534    for( i=j=0; i<(nKeys*2); i+=2, ++j ) {
535        indices[j].key = val->val.l.vals[i].val.s.s;
536        indices[j].index = i;
537    }
538    qsort( indices, j, sizeof(struct KeyIndex), compareKeyIndex );
539    for( i=0; i<j; ++i ) {
540        const int index = indices[i].index;
541        node->children[ node->childCount++ ] = index;
542        node->children[ node->childCount++ ] = index + 1;
543    }
544
545    assert( node->childCount == nKeys * 2 );
546    tr_free( indices );
547    return node;
548}
549
550static struct SaveNode*
551nodeNewList( const tr_benc * val )
552{
553    int i, n;
554    struct SaveNode * node;
555
556    assert( isList( val ) );
557
558    n = val->val.l.count;
559    node = tr_new0( struct SaveNode, 1 );
560    node->val = val;
561    node->childCount = n;
562    node->children = tr_new0( int, n );
563    for( i=0; i<n; ++i ) /* a list's children don't need to be reordered */
564        node->children[i] = i;
565
566    return node;
567}
568
569static struct SaveNode*
570nodeNewLeaf( const tr_benc * val )
571{
572    struct SaveNode * node;
573
574    assert( !isContainer( val ) );
575
576    node = tr_new0( struct SaveNode, 1 );
577    node->val = val;
578    return node;
579}
580
581static struct SaveNode*
582nodeNew( const tr_benc * val )
583{
584    struct SaveNode * node;
585
586    if( isList( val ) )
587        node = nodeNewList( val );
588    else if( isDict( val ) )
589        node = nodeNewDict( val );
590    else
591        node = nodeNewLeaf( val );
592
593    return node;
594}
595
596typedef void (*BencWalkFunc)( const tr_benc * val, void * user_data );
597
598struct WalkFuncs
599{
600    BencWalkFunc intFunc;
601    BencWalkFunc stringFunc;
602    BencWalkFunc dictBeginFunc;
603    BencWalkFunc listBeginFunc;
604    BencWalkFunc containerEndFunc;
605};
606
607/**
608 * This function's previous recursive implementation was
609 * easier to read, but was vulnerable to a smash-stacking
610 * attack via maliciously-crafted bencoded data. (#667)
611 */
612static void
613bencWalk( const tr_benc   * top,
614          struct WalkFuncs   * walkFuncs,
615          void               * user_data )
616{
617    tr_ptrArray * stack = tr_ptrArrayNew( );
618    tr_ptrArrayAppend( stack, nodeNew( top ) );
619
620    while( !tr_ptrArrayEmpty( stack ) )
621    {
622        struct SaveNode * node = tr_ptrArrayBack( stack );
623        const tr_benc * val;
624
625        if( !node->valIsVisited )
626        {
627            val = node->val;
628            node->valIsVisited = TRUE;
629        }
630        else if( node->childIndex < node->childCount )
631        {
632            const int index = node->children[ node->childIndex++ ];
633            val = node->val->val.l.vals +  index;
634        }
635        else /* done with this node */
636        {
637            if( isContainer( node->val ) )
638                walkFuncs->containerEndFunc( node->val, user_data );
639            tr_ptrArrayPop( stack );
640            tr_free( node->children );
641            tr_free( node );
642            continue;
643        }
644
645        switch( val->type )
646        {
647            case TYPE_INT:
648                walkFuncs->intFunc( val, user_data );
649                break;
650
651            case TYPE_STR:
652                walkFuncs->stringFunc( val, user_data );
653                break;
654
655            case TYPE_LIST:
656                if( val != node->val )
657                    tr_ptrArrayAppend( stack, nodeNew( val ) );
658                else
659                    walkFuncs->listBeginFunc( val, user_data );
660                break;
661
662            case TYPE_DICT:
663                if( val != node->val )
664                    tr_ptrArrayAppend( stack, nodeNew( val ) );
665                else
666                    walkFuncs->dictBeginFunc( val, user_data );
667                break;
668
669            default:
670                /* did caller give us an uninitialized val? */
671                tr_err( "Invalid benc type %d", val->type );
672                break;
673        }
674    }
675
676    tr_ptrArrayFree( stack, NULL );
677}
678
679/****
680*****
681****/
682
683static void
684saveIntFunc( const tr_benc * val, void * evbuf )
685{
686    evbuffer_add_printf( evbuf, "i%"PRId64"e", tr_bencGetInt(val) );
687}
688static void
689saveStringFunc( const tr_benc * val, void * vevbuf )
690{
691    struct evbuffer * evbuf = vevbuf;
692    evbuffer_add_printf( evbuf, "%i:", val->val.s.i );
693    evbuffer_add( evbuf, val->val.s.s, val->val.s.i );
694}
695static void
696saveDictBeginFunc( const tr_benc * val UNUSED, void * evbuf )
697{
698    evbuffer_add_printf( evbuf, "d" );
699}
700static void
701saveListBeginFunc( const tr_benc * val UNUSED, void * evbuf )
702{
703    evbuffer_add_printf( evbuf, "l" );
704}
705static void
706saveContainerEndFunc( const tr_benc * val UNUSED, void * evbuf )
707{
708    evbuffer_add_printf( evbuf, "e" );
709}
710char*
711tr_bencSave( const tr_benc * top, int * len )
712{
713    char * ret;
714    struct WalkFuncs walkFuncs;
715    struct evbuffer * out = evbuffer_new( );
716
717    walkFuncs.intFunc = saveIntFunc;
718    walkFuncs.stringFunc = saveStringFunc;
719    walkFuncs.dictBeginFunc = saveDictBeginFunc;
720    walkFuncs.listBeginFunc = saveListBeginFunc;
721    walkFuncs.containerEndFunc = saveContainerEndFunc;
722    bencWalk( top, &walkFuncs, out );
723   
724    if( len != NULL )
725        *len = EVBUFFER_LENGTH( out );
726    ret = tr_strndup( (char*) EVBUFFER_DATA( out ), EVBUFFER_LENGTH( out ) );
727    evbuffer_free( out );
728    return ret;
729}
730
731/***
732****
733***/
734
735static void
736freeDummyFunc( const tr_benc * val UNUSED, void * buf UNUSED  )
737{
738}
739static void
740freeStringFunc( const tr_benc * val, void * freeme )
741{
742    if( !val->val.s.nofree )
743        tr_ptrArrayAppend( freeme, val->val.s.s );
744}
745static void
746freeContainerBeginFunc( const tr_benc * val, void * freeme )
747{
748    tr_ptrArrayAppend( freeme, val->val.l.vals );
749}
750void
751tr_bencFree( tr_benc * val )
752{
753    if( val != NULL )
754    {
755        tr_ptrArray * freeme = tr_ptrArrayNew( );
756        struct WalkFuncs walkFuncs;
757
758        walkFuncs.intFunc = freeDummyFunc;
759        walkFuncs.stringFunc = freeStringFunc;
760        walkFuncs.dictBeginFunc = freeContainerBeginFunc;
761        walkFuncs.listBeginFunc = freeContainerBeginFunc;
762        walkFuncs.containerEndFunc = freeDummyFunc;
763        bencWalk( val, &walkFuncs, freeme );
764
765        tr_ptrArrayFree( freeme, tr_free );
766    }
767}
768
769/***
770****
771***/
772
773struct WalkPrint
774{
775    int depth;
776    FILE * out;
777};
778static void
779printLeadingSpaces( struct WalkPrint * data )
780{
781    const int width = data->depth * 2;
782    fprintf( data->out, "%*.*s", width, width, " " );
783}
784static void
785printIntFunc( const tr_benc * val, void * vdata )
786{
787    struct WalkPrint * data = vdata;
788    printLeadingSpaces( data );
789    fprintf( data->out, "int:  %"PRId64"\n", tr_bencGetInt(val) );
790}
791static void
792printStringFunc( const tr_benc * val, void * vdata )
793{
794    int ii;
795    struct WalkPrint * data = vdata;
796    printLeadingSpaces( data );
797    fprintf( data->out, "string:  " );
798    for( ii = 0; val->val.s.i > ii; ii++ )
799    {
800        if( '\\' == val->val.s.s[ii] ) {
801            putc( '\\', data->out );
802            putc( '\\', data->out );
803        } else if( isprint( val->val.s.s[ii] ) ) {
804            putc( val->val.s.s[ii], data->out );
805        } else {
806            fprintf( data->out, "\\x%02x", val->val.s.s[ii] );
807        }
808    }
809    fprintf( data->out, "\n" );
810}
811static void
812printListBeginFunc( const tr_benc * val UNUSED, void * vdata )
813{
814    struct WalkPrint * data = vdata;
815    printLeadingSpaces( data );
816    fprintf( data->out, "list\n" );
817    ++data->depth;
818}
819static void
820printDictBeginFunc( const tr_benc * val UNUSED, void * vdata )
821{
822    struct WalkPrint * data = vdata;
823    printLeadingSpaces( data );
824    fprintf( data->out, "dict\n" );
825    ++data->depth;
826}
827static void
828printContainerEndFunc( const tr_benc * val UNUSED, void * vdata )
829{
830    struct WalkPrint * data = vdata;
831    --data->depth;
832}
833void
834tr_bencPrint( const tr_benc * val )
835{
836    struct WalkFuncs walkFuncs;
837    struct WalkPrint walkPrint;
838
839    walkFuncs.intFunc = printIntFunc;
840    walkFuncs.stringFunc = printStringFunc;
841    walkFuncs.dictBeginFunc = printDictBeginFunc;
842    walkFuncs.listBeginFunc = printListBeginFunc;
843    walkFuncs.containerEndFunc = printContainerEndFunc;
844
845    walkPrint.out = stderr;
846    walkPrint.depth = 0;
847    bencWalk( val, &walkFuncs, &walkPrint );
848}
Note: See TracBrowser for help on using the repository browser.