Convert::Binary::C で C 構造体をバイナリ変換 (基本)
C の構造体の書き方で、バイナリ変換したい。組込み関数の pack と unpack でもできるけど直感的でない。 Convert::Binary::C を使うと C の構造体にハッシュをつっこんでバイナリ変換できる。
ヘッダファイル
以下のようなヘッダファイルにある構造体を使うとする。
basic.h
#ifndef BASIC_H #define BASIC_H typedef unsigned char u_char; typedef unsigned short u_short; typedef unsigned int u_int; typedef unsigned long u_long; #define IPVERSION 4 #pragma pack(1) struct ip { u_int ip_v:4; /* version */ u_int ip_hl:4; /* header length */ u_char ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ u_char ip_ttl; /* time to live */ u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ u_int ip_src; /* source address */ u_int ip_dst; /* dest address */ }; #pragma pack #endif
use
Data::Hexdumper は、バイナリデータを表示するとき便利。
use Convert::Binary::C; use Data::Hexdumper;
オブジェクト作成、設定、parse
confiure で色々設定できる。parse_file() で ヘッダファイルを指定。parse() で C の構造体もperlスクリプトの中に書ける。
my $c = Convert::Binary::C->new(); $c->configure( ByteOrder => 'BigEndian' ); $c->parse_file('./basic.h');
変換前のデータ
変換前のデータはハッシュで表現する。
my %ip_header_data = ( ip_v => $ip_version, # 4 ip_hl => $ip_header_size * 8 / 32, # 5 (0x05) ip_tos => '0x12', # 18 ip_len => $ip_header_size + 12, # 32 (0x20) ip_id => '0x1234', # 4660 ip_off => '0x5678', # 22136 ip_ttl => '0x9A', # 154 ip_p => '0xBC', # 188 ip_sum => '0xDEF0', # 57072 ip_src => '0x01020304', # 16909060 ip_dst => '0x0A0B0C0D', # 168496141 );
pack
pack() で一気にバイナリ化できる。
my $packed = $c->pack( 'ip', \%ip_header_data ); print hexdump( 'data' => $packed );
出力
0x0000 : 45 12 00 20 12 34 56 78 9A BC DE F0 01 02 03 04 : E....4Vx........ 0x0010 : 0A 0B 0C 0D : ....
unpack
unpack でバイナリデータをハッシュに変換できる。
my $unpacked = $c->unpack( 'ip', $packed ); print Dumper($unpacked);
出力
$VAR1 = { 'ip_hl' => 5, 'ip_off' => 22136, 'ip_p' => 188, 'ip_ttl' => 154, 'ip_len' => 32, 'ip_id' => 4660, 'ip_v' => 4, 'ip_dst' => 168496141, 'ip_sum' => 57072, 'ip_src' => 16909060, 'ip_tos' => 18 };
役に立ちそうなメソッド
member, sizeof, type, macro
全体
#!/usr/bin/perl use strict; use warnings; use Carp; use Convert::Binary::C; use Data::Dumper; use Data::Hexdumper; # new method my $c = Convert::Binary::C->new(); # configure method # Network byte order $c->configure( ByteOrder => 'BigEndian' ); # parse_file method # Target file $c->parse_file('./basic.h'); # sizeof method my $ip_header_size = $c->sizeof('ip'); print '### sizeof', $/; print 'ip header size: ', $ip_header_size, $/; # macro method my ( undef, $ip_version ) = split( /\s+/, $c->macro('IPVERSION'), 2 ); print '### macro', $/; print 'ip version : ', $ip_version, $/; # data my %ip_header_data = ( ip_v => $ip_version, # 4 ip_hl => $ip_header_size * 8 / 32, # 5 (0x05) ip_tos => '0x12', # 18 ip_len => $ip_header_size + 12, # 32 (0x20) ip_id => '0x1234', # 4660 ip_off => '0x5678', # 22136 ip_ttl => '0x9A', # 154 ip_p => '0xBC', # 188 ip_sum => '0xDEF0', # 57072 ip_src => '0x01020304', # 16909060 ip_dst => '0x0A0B0C0D', # 168496141 ); # pack method my $packed = $c->pack( 'ip', \%ip_header_data ); print '### pack', $/; print hexdump( 'data' => $packed ); # unpack method my $unpacked = $c->unpack( 'ip', $packed ); print '### unpack', $/; print Dumper($unpacked); # offset, type, size method sub print_info { my ( $struct_name, $c, $packed_ref ) = @_; while ( my ( $member, $value ) = each %$packed_ref ) { my $absolute_name = $struct_name . '.' . $member; print "--- member $member", $/; print "absolute : $absolute_name\n"; my $type = $c->typeof($absolute_name); print "type : ", $type, $/; # Skip bit field type if ( $type !~ m/^.*:\d+$/ ) { print "size : ", $c->sizeof($absolute_name), $/; print "offset : ", $c->offsetof( $struct_name, $member ), $/; } print "value : $value", $/; } } print '### print each member info', $/; print_info( 'ip', $c, $unpacked ); # member method print '### member method', $/; for my $offset ( 0 .. 10 ) { print "\$c->member('ip', $offset)"; my $member = eval { $c->member( 'ip', $offset ) }; print $@ ? "\n exception: $@" : " => '$member'\n"; }
結果
### sizeof ip header size: 20 ### macro ip version : 4 ### pack 0x0000 : 45 12 00 20 12 34 56 78 9A BC DE F0 01 02 03 04 : E....4Vx........ 0x0010 : 0A 0B 0C 0D : .... ### unpack $VAR1 = { 'ip_hl' => 5, 'ip_off' => 22136, 'ip_p' => 188, 'ip_ttl' => 154, 'ip_len' => 32, 'ip_id' => 4660, 'ip_v' => 4, 'ip_dst' => 168496141, 'ip_sum' => 57072, 'ip_src' => 16909060, 'ip_tos' => 18 }; ### print each member info --- member ip_hl absolute : ip.ip_hl type : u_int :4 value : 5 --- member ip_off absolute : ip.ip_off type : u_short size : 2 offset : 6 value : 22136 --- member ip_p absolute : ip.ip_p type : u_char size : 1 offset : 9 value : 188 --- member ip_ttl absolute : ip.ip_ttl type : u_char size : 1 offset : 8 value : 154 --- member ip_len absolute : ip.ip_len type : u_short size : 2 offset : 2 value : 32 --- member ip_id absolute : ip.ip_id type : u_short size : 2 offset : 4 value : 4660 --- member ip_v absolute : ip.ip_v type : u_int :4 value : 4 --- member ip_dst absolute : ip.ip_dst type : u_int size : 4 offset : 16 value : 168496141 --- member ip_sum absolute : ip.ip_sum type : u_short size : 2 offset : 10 value : 57072 --- member ip_src absolute : ip.ip_src type : u_int size : 4 offset : 12 value : 16909060 --- member ip_tos absolute : ip.ip_tos type : u_char size : 1 offset : 1 value : 18 ### member method $c->member('ip', 0) => '+0' $c->member('ip', 1) => '.ip_tos' $c->member('ip', 2) => '.ip_len' $c->member('ip', 3) => '.ip_len+1' $c->member('ip', 4) => '.ip_id' $c->member('ip', 5) => '.ip_id+1' $c->member('ip', 6) => '.ip_off' $c->member('ip', 7) => '.ip_off+1' $c->member('ip', 8) => '.ip_ttl' $c->member('ip', 9) => '.ip_p' $c->member('ip', 10) => '.ip_sum'