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
                         }
                       ]
        };