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'