Convert::Binary::C で 詰まった C 共用体をバイナリ変換 (うまくいかない)
C の共用体のサイズは、サイズの大きいメンバが基準になってしまう。ネットワークで C の共用体のような使い方をしたいときがあるけど、データが詰まった状態になって欲しい。つまり、
struct packet_t { struct ip_hdr_t ip_hdr; union { struct tcp_hdr_t tcp_hdr; struct udp_hdr_t udp_hdr; } uni; u_char payload[1]; };
とした場合、tcp_hdr に引きずられるのではなくて、udp_hdr を使うときは共用体のサイズがsizeof(udp_hdr)となって欲しい。
Convert::Binary::C では、詰まった共用体を実現できるのはないかと思ったが、どうも上手くいかない。u_char payload[1] があって、 dimension が 0 の場合、普通の共用体になってしまう ?
ヘッダファイル
union_network.h
typedef unsigned char u_char; typedef unsigned short u_short; typedef unsigned int u_int; typedef unsigned long u_long; #pragma pack(1) struct ip_hdr_t { u_char protocol; }; struct udp_hdr_t { u_short hdr_data; }; struct tcp_hdr_t { u_int hdr_data; }; struct packet_t { struct ip_hdr_t ip_hdr; struct tcp_hdr_t tcp_hdr[1]; // ip_hdr.type = 6 struct udp_hdr_t udp_hdr[1]; // ip_hdr.type = 17 u_char payload[5]; }; #pragma pack
変換前のデータ
my %test_data = ( ip_hdr => { protocol => 17 }, tcp_hdr => [ { hdr_data => 0x12345678 } ], udp_hdr => [ { hdr_data => 0xABCD } ], payload => [ 0x0A, 0x0B, 0x0C, 0x0D, 0x0E ], );
dimension
# tcp header dimension $c->tag( 'packet_t.tcp_hdr', Dimension => sub { return ( $_[0]->{ip_hdr}->{protocol} == 6 ? 1 : 0 ); } ); # udp header dimension $c->tag( 'packet_t.udp_hdr', Dimension => sub { return ( $_[0]->{ip_hdr}->{protocol} == 17 ? 1 : 0 ); } );
pack
my $binary = $c->pack( 'packet_t', \%test_data ); print hexdump( data => $binary );
0x0000 : 11 00 00 00 00 AB CD 0A 0B 0C 0D 0E : ............
udp_hdr のデータ 0xABCD だけ出てきて、「11 AB CD 0A 0B 0C 0D 0E 」となって欲しいけど、tcp_hdr の分、余計な 「00 00 00 00」が出てしまう。
unpack
my $unpacked = $c->unpack( 'packet_t', $binary ); print Dumper($unpacked);
$VAR1 = { 'ip_hdr' => { 'protocol' => 17 }, 'tcp_hdr' => [], 'payload' => [ 10, 11, 12, 13, 14 ], 'udp_hdr' => [ { 'hdr_data' => 43981 } ] };
テストコード
#!/usr/bin/perl use strict; use warnings; use Carp; use Convert::Binary::C; use Data::Dumper; use Data::Hexdumper; ## 未完 ## dimension を 0 にできない my $c = Convert::Binary::C->new(); $c->configure( ByteOrder => 'BigEndian' ); $c->parse_file('./union_network.h'); # data my %test_data = ( ip_hdr => { protocol => 17 }, tcp_hdr => [ { hdr_data => 0x12345678 } ], udp_hdr => [ { hdr_data => 0xABCD } ], payload => [ 0x0A, 0x0B, 0x0C, 0x0D, 0x0E ], ); # tcp header dimension $c->tag( 'packet_t.tcp_hdr', Dimension => sub { return ( $_[0]->{ip_hdr}->{protocol} == 6 ? 1 : 0 ); } ); # udp header dimension $c->tag( 'packet_t.udp_hdr', Dimension => sub { return ( $_[0]->{ip_hdr}->{protocol} == 17 ? 1 : 0 ); } ); # pack my $binary = $c->pack( 'packet_t', \%test_data ); print '--- pack', $/; print hexdump( data => $binary ); # unpack my $unpacked = $c->unpack( 'packet_t', $binary ); print '--- unpack', $/; print Dumper($unpacked);
--- pack 0x0000 : 11 00 00 00 00 AB CD 0A 0B 0C 0D 0E : ............ --- unpack $VAR1 = { 'ip_hdr' => { 'protocol' => 17 }, 'tcp_hdr' => [], 'payload' => [ 10, 11, 12, 13, 14 ], 'udp_hdr' => [ { 'hdr_data' => 43981 } ] };