Symfoware

Symfowareについての考察blog

PythonでBencodingのデコード(なぜなに Torrent)

なぜなに Torrent ( P2P プログラム入門 )

こちらを参考に、PythonでTorrentクライアントを作ってみます。
最終目標は、Debianのインストーラーをダウンロードしてみること。

最小の CD を使って、ネットワークインストールする



Torrentファイルを読み込む



https://nazenani-torrent.firefirestyle.net/torrentfile/Torrentfile.html

Torrentファイルは、bencodeという形式で記載されているそうです。
データ型は4つ。


・文字列(String)

[文字の長さ(バイト数)]:[文字]

という形式で表現。

「Hello」は、「5:Hello」
「こんにちは」は、「10:こんにちは」
となるようです。



・整数(Number)

i[数字]e

とい形式で表現。

「123」は「i123e」
「1024」は「i1024e」
となるようです。



・リスト(List)

順序を持った複数のデータを保持します。


l[要素1][要素2]...[要素n]e

という形式で表現。


Pythonでの配列
['Hello', 'こんにちは']、これは「l5:Hello10:こんにちはe」
[123, 1024]、これは「li123ei1024ee」
となるようです。



・辞書(Dict)

キーワードと値を関連付けてデータを保持します。

d[キー要素1][値要素1][キー要素2][値要素2]...[キー要素n][値要素n]e

という形式で表現。

キーが「Hello」、値が「こんにちは」の場合「d5:Hello10:こんにちはe」
となるようです。





torrentファイルの内容




実際のtorrentファイルの中身を確認してみます。

最小の CD を使って、ネットワークインストールする
こちらから「debian-8.7.1-amd64-netinst.iso.torrent」をダウンロード。

バイナリエディタで内容を確認してみます。

750_01.png

「d」から始まるので辞書型で、次は数値なので、コロン以降に文字列が続いて...
確かに読み取れますね。




パーサー



適当にパーサーを書いて、ファイルの中身を解析してみます。


  1. # -*- coding:utf-8 -*-
  2. class MyTorrent(object):
  3.     def parse_file(self, torrent_file):
  4.         
  5.         with open(torrent_file, 'rb') as f:
  6.             result = self.parse(f.read())
  7.             
  8.         return result
  9.     
  10.     
  11.     def parse(self, bencode_bytes):
  12.         
  13.         result = []
  14.         while(bencode_bytes):
  15.         
  16.             # 数値
  17.             if bencode_bytes[0] == 'i':
  18.                 v,bencode_bytes = self.parse_number(bencode_bytes)
  19.                 
  20.             # リスト
  21.             elif bencode_bytes[0] == 'l':
  22.                 v,bencode_bytes = self.parse_list(bencode_bytes)
  23.                 
  24.             # 辞書
  25.             elif bencode_bytes[0] == 'd':
  26.                 v,bencode_bytes = self.parse_dict(bencode_bytes)
  27.             
  28.             # 解析途中で打ち切り
  29.             elif bencode_bytes[0] == 'e':
  30.                 return (result, bencode_bytes[1:])
  31.             
  32.             # 文字列
  33.             else:
  34.                 v,bencode_bytes = self.parse_string(bencode_bytes)
  35.             
  36.             result.append(v)
  37.             
  38.         return result
  39.     
  40.     
  41.     def parse_number(self, bencode_bytes):
  42.         """ 数値部分を解析し、解析結果と未解析部分を返す """
  43.         index = bencode_bytes.find('e')
  44.         value = int(bencode_bytes[1:index])
  45.         return (value, bencode_bytes[index + 1:])
  46.         
  47.     
  48.     def parse_string(self, bencode_bytes):
  49.         """ 文字列部分を解析し、解析結果と未解析部分を返す """
  50.         index = bencode_bytes.find(':')
  51.         last_index = index + int(bencode_bytes[:index]) + 1
  52.         value = bencode_bytes[index + 1:last_index]
  53.         return (value, bencode_bytes[last_index:])
  54.         
  55.     def parse_list(self, bencode_bytes):
  56.         """ リスト部分を解析し、解析結果と未解析部分を返す """
  57.         return self.parse(bencode_bytes[1:])
  58.         
  59.     def parse_dict(self, bencode_bytes):
  60.         """ 辞書部分を解析し、解析結果と未解析部分を返す """
  61.         list_value, bencode_bytes = self.parse(bencode_bytes[1:])
  62.         value = {}
  63.         for i in range(0, len(list_value), 2):
  64.             value[list_value[i]] = list_value[i + 1]
  65.         
  66.         return value, bencode_bytes
  67. if __name__ == '__main__':
  68.     t = MyTorrent()
  69.     result = t.parse_file('debian-8.7.1-amd64-netinst.iso.torrent')
  70.     info = result[0]
  71.     
  72.     print('announce', info['announce'])
  73.     print('creation date', info['creation date'])
  74.     print('comment', info['comment'])
  75.     print('httpseeds', info['httpseeds'])
  76.     print('info.length', info['info']['length'])
  77.     print('info.piece length', info['info']['piece length'])
  78.     print('info.name', info['info']['name'])
  79.     print('info.pieces', len(info['info']['pieces']))





実行結果


$ python sample.py
('announce', 'http://bttracker.debian.org:6969/announce')
('creation date', 1484567799)
('comment', '"Debian CD from cdimage.debian.org"')
('httpseeds', ['http://cdimage.debian.org/cdimage/release/8.7.1/iso-cd/debian-8.7.1-amd64-netinst.iso', 'http://cdimage.debian.org/cdimage/archive/8.7.1/iso-cd/debian-8.7.1-amd64-netinst.iso'])
('info.length', 260046848)
('info.piece length', 262144)
('info.name', 'debian-8.7.1-amd64-netinst.iso')
('info.pieces', 19840)





・announce:Tracker サーバーのアドレス

http://bttracker.debian.org:6969/announce
ここに問い合わせれば、データを配信してくれる端末を紹介してくれるようです。


・creation date:ファイルの作成日


・comment:コメント



・info.length:ダウンロード後のサイズ

260046848 byteなので、260.0 MBぐらいになります。


・info.piece length:データを配信する際に分割するサイズ

262144 byteなので、256KBごとにファイルが分割されているようです。

ファイルの総容量 260046848 byte / 分割サイズ 262144 byte = 992

992個のファイルに分割されていると読み取れるのかと思います。


・info.name:ファイル名

debian-8.7.1-amd64-netinst.iso


・info.pieces:分割されたデータごとのHash値

20バイト単位のバイナリデータとのこと。

19840 byte / 20 byte = 992

上記で計算した分割ファイル数と一致しますね。




次は、UPnPによるポートマップを試してみようと思います。




関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2017/05/05(金) 18:28:42|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
前のページ