Module: BEncode

Defined in:
ext/bencode_ext/bencode.c,
ext/bencode_ext/bencode.c

Overview

BEncode module provides functionality for encoding/decoding Ruby objects in BitTorrent loosly structured data format - bencode. To decode data you sould use one of the following:

'string'.bdecode
BEncode.decode('string')

Where string contains bencoded data (i.e. some torrent file) To encode your objects into bencode format:

object.bencode
# or
BEncode.encode(object)

bencode format has only following datatypes:

Integer
String
List
Dictionary (Hash)

No other types allowed. Only symbols are converted to string for convenience (but they’re decoded as strings).

BEncode is included into Object on load.

Defined Under Namespace

Classes: DecodeError, EncodeError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.decode(string) ⇒ Object

Returns data structure from parsed string. String must be valid bencoded data, or BEncode::DecodeError will be raised with description of error.

Examples:

BEncode.decode('i1e') => 1
BEncode.decode('i-1e') => -1
BEncode.decode('6:string') => 'string'


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'ext/bencode_ext/bencode.c', line 69

static VALUE decode(VALUE self, VALUE encoded){
  long  len, rlen;
  char* str;
  VALUE ret, container_stack, current_container, key, crt;

  if(!rb_obj_is_kind_of(encoded, rb_cString))
    rb_raise(rb_eTypeError, "String expected");

  len = rlen = RSTRING_LEN(encoded);
  if(!len)
    return Qnil;

  str = RSTRING_PTR(encoded);
  container_stack = rb_ary_new();
  current_container = ret = key = Qnil;

  while(len){
    int state = ELEMENT_SCALAR;
    switch(*str){
      case 'l':
      case 'd':
        crt = *str == 'l' ? rb_ary_new() : rb_hash_new();
        NEXT_CHAR;
        if(NIL_P(current_container)){
          if(max_depth == 0)
            rb_raise(DecodeError, "Structure is too deep!");
          ret = current_container = crt;
          continue;
        }
        state = ELEMENT_STRUCT;
        break;
      case 'i':
        NEXT_CHAR;
        crt = LONG2FIX(parse_num(&str, &len));

        if(!len)
          rb_raise(DecodeError, "Unpexpected integer end!");
        if(*str != 'e')
          rb_raise(DecodeError, "Mailformed integer at %d byte: %c", rlen - len, *str);

        NEXT_CHAR;
        break;
      case '0'...'9':{
        long slen = parse_num(&str, &len);

        if(len && *str != ':')
          rb_raise(DecodeError, "Invalid string length specification at %d: %c", rlen - len, *str);
        if(!len || len < slen + 1)
          rb_raise(DecodeError, "Unexpected string end!");

        crt = rb_str_new(++str, slen);
        str += slen;
        len -= slen + 1;
        break;
      }
      case 'e':
        if(NIL_P(current_container))
          rb_raise(DecodeError, "Unexpected container end at %d!", rlen - len);
        current_container = rb_ary_pop(container_stack);
        state = ELEMENT_END;
        NEXT_CHAR;
        break;
      default:
        rb_raise(DecodeError, "Unknown element type at %d: %c!", rlen - len, *str);
    }

    if(NIL_P(current_container)){
      if(NIL_P(ret))
        ret = crt;
      break;
    }else if(state != ELEMENT_END){
      if(BUILTIN_TYPE(current_container) == T_ARRAY){
        rb_ary_push(current_container, crt);
      }else if(NIL_P(key)){
        if(BUILTIN_TYPE(crt) != T_STRING)
          rb_raise(DecodeError, "Dictionary key must be a string (at %d)!", rlen - len);
        key = crt;
      }else{
        rb_hash_aset(current_container, key, crt);
        key = Qnil;
      }

      if(state == ELEMENT_STRUCT){
        rb_ary_push(container_stack, current_container);
        if(max_depth != -1 && max_depth < RARRAY_LEN(container_stack) + 1)
          rb_raise(DecodeError, "Structure is too deep!");
        current_container = crt;
      }
    }
  }

  if(len)
    rb_raise(DecodeError, "String has garbage on the end (starts at %d).", rlen - len);
  else if(!NIL_P(current_container))
    rb_raise(DecodeError, "Unpexpected end of %s.", BUILTIN_TYPE(current_container) == T_HASH ? "dictionary" : "list");

  return ret;
}

.decode_file(file) ⇒ Object

Loads content of file and decodes it. file may be either IO instance or String path to file.

Examples:

BEncode.decode_file('/path/to/file.torrent')

open('/path/to/file.torrent', 'rb') do |f|
  BEncode.decode_file(f)
end


190
191
192
193
194
195
196
197
# File 'ext/bencode_ext/bencode.c', line 190

static VALUE decode_file(VALUE self, VALUE path){
  if(rb_obj_is_kind_of(path, rb_cIO)){
    return _decode_file(path);
  }else{
    VALUE fp = rb_file_open_str(path, "rb");
    return rb_ensure(_decode_file, fp, rb_io_close, fp);
  }
}

.encode(object) ⇒ Object

Shortcut to object.bencode



287
288
289
# File 'ext/bencode_ext/bencode.c', line 287

static VALUE mod_encode(VALUE self, VALUE x){
  return encode(x);
}

.max_depthObject

Get maximum depth of parsed structure.



299
300
301
# File 'ext/bencode_ext/bencode.c', line 299

static VALUE get_max_depth(VALUE self){
  return LONG2FIX(max_depth);
}

.max_depth=(_integer_) ⇒ Object

Sets maximum depth of parsed structure. Expects integer greater or equal to 0. By default this value is 5000. Assigning nil will disable depth check.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'ext/bencode_ext/bencode.c', line 314

static VALUE set_max_depth(VALUE self, VALUE depth){
  long t;

  if(NIL_P(depth)){
    max_depth = -1;
    return depth;
  }

  if(!rb_obj_is_kind_of(depth, rb_cInteger))
    rb_raise(rb_eArgError, "Integer expected!");

  t = NUM2LONG(depth);
  if(t < 0)
    rb_raise(rb_eArgError, "Depth must be greather than or equal to 0");

  max_depth = t;
  return depth;
}

Instance Method Details

#bencodeObject

Returns a string representing object in bencoded format. object must be one of:

Integer
String
Symbol (will be converter to String)
Array
Hash

If object does not belong to these types or their derivates BEncode::EncodeError exception will be raised.

Because BEncode is included into Object This method is avilable for all objects.

Examples:

1.bencode => 'i1e'
-1.bencode => 'i-1e'
'string'.bencode => '6:string'


225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'ext/bencode_ext/bencode.c', line 225

static VALUE encode(VALUE self){
  if(TYPE(self) == T_SYMBOL)
    return encode(rb_id2str(SYM2ID(self)));

  if(rb_obj_is_kind_of(self, rb_cString)){
    long len = RSTRING_LEN(self);
    return rb_sprintf("%ld:%.*s", len, len, RSTRING_PTR(self));
  }
  
  if(rb_obj_is_kind_of(self, rb_cInteger))
    return rb_sprintf("i%lde", NUM2LONG(self));
  
  if(rb_obj_is_kind_of(self, rb_cHash)){
    VALUE ret = rb_str_new2("d");
    rb_hash_foreach(self, hash_traverse, ret);
    rb_str_cat2(ret, "e");
    return ret;
  }
  
  if(rb_obj_is_kind_of(self, rb_cArray)){
    long i, c;
    VALUE *ptr, ret = rb_str_new2("l");

    for(i = 0, c = RARRAY_LEN(self), ptr = RARRAY_PTR(self); i < c; ++i)
      rb_str_concat(ret, encode(ptr[i]));

    rb_str_cat2(ret, "e");
    return ret;
  }

  rb_raise(EncodeError, "Don't know how to encode %s!", rb_class2name(CLASS_OF(self)));
}