■
BitTorrentのメタ情報ファイルや、
トラッカーの応答メッセージで使われている
BEncode(bee-encodeと発音する)をRubyで実装してみた。
Rythonは、オリジナルのスクリプトに含まれているし、
Perl, OCamlによる実装はすでにあるらしい。&&
PHPには、若干仕様が異なるが、似たようなアルゴリズムの
serialize, unserializeという組み込み関数がある。
...ということで、ひさしぶりにRuby.
ルールはシンプル。次の通り。
- 文字列 length `:' data
- 整数 'i' data 'e'
- リスト 'l' ... 'e'
- 辞書 'd' ... 'e'
例を幾つか上げると、
"4:SPAM"からは、':'以降の4bytes分のデータ"SPAM".
"i123e"は、'i'から次に現れる'e'までの数 123 がデータ部,
次にリスト構造。"li10ei20ei30ee"は、最初の'l'と最後の'e'
が対応していて、間には数値の要素が三つ[10,20,30]となる。
辞書もリストの応用で、違いは、辞書では連続する2つの要素
はキーと値のペアになって辞書(Rubyではハッシュ構造)に格納される。また、リストと辞書はそれぞれ入れ子にする事も出来る。
require 'stringio' class UnknownCommandError < StandardError end class InvalidNumberFormatError < StandardError end def bdecode(stream, nesting=0) if stream.member? "getc" # FIXME stream type ¤ÎȽÃÇ return _bdecode(stream, nesting) else return _bdecode(StringIO.new(stream), nesting) end end def _read_bytes(stream, terminator) tmp = Array.new loop do c = stream.getc if c && c.chr != terminator && c >= 0 tmp.push(c.chr) else break end end return tmp.join('') end def _read_number(stream, terminator) num = _read_bytes(stream, terminator) if num.match(/^(0|-?[1-9][0-9]*)$/) return num.to_i else raise InvalidNumberFormatError end end def _bdecode(stream, nesting=0) c = stream.getc case c.chr when 'd' # Dict tmp = Hash.new while key = _bdecode(stream, nesting+1) do val = _bdecode(stream, nesting+1) tmp[key] = val end return tmp when 'l' # List tmp = Array.new while val = _bdecode(stream, nesting+1) do tmp.push(val) end return tmp when 'i' # Integer return _read_number(stream, 'e') when '0' .. '9' # String stream.ungetc(c) len = _read_number(stream, ':') str = stream.read(len) return str when 'e', -1 return nil else raise UnknownCommandError end end p bdecode("d4:name7:teamikl3:agei24ee")