2014年1月23日木曜日

packメソッドについて補足

router-utilsのソースを読んでいて、最も難解なのがpackメソッドでした。
補足などと言えるほど理解していないかもですが、確認したことをメモしておきます。

例えば"abc123"という文字列をunpack("C*")すると[97, 98, 99, 49, 50, 51]という配列が返ってきます。
配列の数字は文字コードのようです。aの文字コードは97、1の文字コードは49です。

(pack.rb)
p "abc123".unpack("C*")]

$ ruby pack.rb
[97, 98, 99, 49, 50, 51]

次に、unpack("C*")の後にpack("C*").unpack("n*")を追加すると、配列をバイナリにし、その後ビッグエンディアンの16bit 符号なし整数にします。

p "abc123".unpack("C*").pack("C*").unpack("n*")

$ ruby pack.rb
[24930, 25393, 12851]

これはどうなったのかというと、配列の1つ目の24930は97と98をくっつけたものです。

(10進数)→(2進数)
24930→110000101100010

97→1100001
98→1100010

桁を揃えて結合すると同じ値になります

24930→0110000101100010
97,98→0110000101100010

99と49、50と51も同様です。

packのテンプレートの"C"が8bit 符号なし整数なので、丁度配列2つ分です。

続いて、テンプレート文字の"n"は何なのか。
16進数の0xffをpackしてみます。

p [0xff].pack("n*")

$ ruby pack.rb
"\000\377"

0xffの8進数表現になりました。

他の数も試してみます。
p [0x01ff].pack("n*")
p [0x0102ff].pack("n*")

$ ruby pack.rb
"\001\377"
"\002\377"

どうやら8ビットずつを8進数で表示したようです。
0x0102ffは後ろから16ビットだけが変換されたようで、数字が大きいと切られてしまうみたいですね。

router-utilsのソースで見てみます。
例えばICMPEchoReplyクラスの↓この辺など。

124      words = @payload.pack( "C*" ).unpack( "n*" )
125      words.each do | each |
126        @checksum = get_checksum( @checksum, each )
127      end

呼び出し元にさかのぼっていくと元データはmessage.dataです。
message.dataはネットワークに流れるデータなのでバイナリなのでしょう。
unpack("C*")で8bit 符号なし整数の配列にしています。

152      payload = message.data.unpack( "C*" )[ offset .. message.data.length - 1 ]
153      icmp = ICMPEchoReply.new( payload )

その後、(124行目)ICMPEchoReplyクラスでpack("C*")を実行して再びバイナリに変換し、unpack("n*")でビッグエンディアンの16bit 符号なし整数に変換しています。
IPヘッダやICMPヘッダのチェックサム(get_checksumメソッドで計算)は16ビットずつ計算していくので、unpack("n*")で16bitに変換して配列をループで処理しているようです。

以上になります。

0 件のコメント:

コメントを投稿