解包第一个字节表示长度的数据结构

Posted

技术标签:

【中文标题】解包第一个字节表示长度的数据结构【英文标题】:unpacking a data structure whose first byte indicates length 【发布时间】:2020-04-06 20:31:19 【问题描述】:

我正在尝试从 IBM AFP 格式文件中解压缩 TLE(标记的逻辑元素)。

规范 (http://www.afpcinc.org/wp-content/uploads/2017/12/MODCA-Reference-09.pdf) 表明这是两个三元组(即使有四个值),其结构如下(带有它们的字节偏移量):

0:Tlength | 1:Tid | 2-n:参数(= 2:类型+3:格式+4-n:EBCDIC编码字符串)

示例(有两个三元组,一个表示名称,一个表示值):

0C 02  0B  00   C3 A4 99 99 85 95 83 A8    07 36  00 00    C5 E4 D9
12 KEY UID CHAR  C  u  r  r  e  n  c  y     7 VAL RESERVED  E  U  R

我使用 Perl 解析如下(并且成功):

            if ($key eq 'Data') 
                my $tle = $member->struct->$key;
                my $k_length = hex(unpack('H2', substr($tle, 0, 1)));
                my $key = decode('cp500', substr($tle, 4, $k_length - 4));
                my $v_length = hex(unpack('H2', substr($tle, $k_length, 1)));
                my $value = decode('cp500', substr($tle, $k_length + 4, $v_length - 4));
                print("'$key' => '$value'\n");
            

结果:

'货币' => '欧元'

虽然上述方法是成功的,但我觉得我的方法有点过于复杂,并且有一种更有效的方法可以做到这一点。例如。 pack 模板是否支持读取前 n 个字节以用作解包多少个连续字节的量词?我阅读了 Perl 包教程,但似乎找不到类似的内容。

【问题讨论】:

hex(unpack('H2', substr($tle, 0, 1))) 是一种奇怪的写法unpack("C", $tle) 你是对的。为什么“char”模板会产生实际数字? 因为 char 是一个数字 :) 在现代系统上它是一个 8 位数字,但情况并非总是如此,专用硬件可能也并非如此。但它在任何运行 Perl 的系统上都是 8 位的。 谢谢。我猜'char'这个名字让我失望了。好资料。您还有什么建议简化或首先阅读长度,然后以正确的方式解析其余部分? 我认为三胞胎不一定会按照这个顺序出现。即使它们必须按该顺序出现,标签逻辑元素的描述也表明除了 Fully Qualified Name X'02' 和 Attribute Value X'36' 之外的三元组可以发生在 TLE 中。我的意思是,你假设你只会得到一个键和一个值并且按照这个顺序的整体方法是有缺陷的。 【参考方案1】:

如果长度字段不包含自身,您可以执行以下操作:

(my $record, $unparsed) = unpack("C/a a*", $unparsed);
my $key = decode("cp500", unpack("x3 a*", $record));

但长度字段包含自身。

(my $length, $unparsed) = unpack("C a*", $unparsed);
(my $record, $unparsed) = unpack("a".($length-1)." a*", $unparsed);
my $key = decode("cp500", unpack("x3 a*", $record));

【讨论】:

【参考方案2】:

请查看以下演示代码是否满足您的要求。

这段代码

定义散列解码器子程序

DATA 块中读取 OP 提供的字节的十六进制表示

利用pack将读取的数据转换为二进制表示$data

利用unpack提取lengthkey/tidtype

为这个特定的type

调用decoder子例程

获取包含两个数组keysvals

的哈希值

用提供的 keysvals

形成新的散列 %data

输出键和值(返回的用于保留字节/字段顺序)

注意:Encode 'from_to' 用于解码 EBCDIC -- alternative

use strict;
use warnings;
use feature 'say';

use utf8;
use Encode 'from_to';

my $debug = 1;

my %decoder = ( 
                1 => \&decode_type1,
                2 => \&decode_currency,
                3 => \&decode_type3,
                4 => \&decode_type4,
                5 => \&decode_type5
            );

my $bytes = read_bytes();
my($len,$key,$type) = unpack('C3',$bytes);

my $data = $decoder$type($bytes);

my %data;
@data@$data->keys = @$data->vals;

say '
 Unpacked data
---------------';
printf "%-8s => %s\n", $_, $data$_ for @$data->keys;

sub read_bytes 
    my $hex_bytes = <DATA>;

    chomp $hex_bytes;

    my $bytes = pack('H*',$hex_bytes);

    return $bytes;


sub show_bytes 
    my $data = shift;

    print "Bytes: ";
    printf "%02X ", $_ for unpack 'C*', $data;
    print "\n";


sub decode_type1 
    my $bytes = shift;

    return  keys => 'type1', vals => 'vals1' ;


sub decode_currency 
    my $bytes = shift;

    show_bytes($bytes) if $debug;

    my @keys = qw/length_1 key uid char data_1 length_2 val reserved data_2/;
    my @vals = unpack('C4A8C2SA3',$bytes);

    from_to($vals[4], 'cp37', 'latin1');
    from_to($vals[8], 'cp37', 'latin1');
    
    return  keys => \@keys, vals => \@vals;


sub decode_type3 
    my $bytes = shift;

    return  keys => 'type3', vals => 'vals3' ;


sub decode_type4 
    my $bytes = shift;

    return  keys => 'type4', vals => 'vals4' ;


sub decode_type5 
    my $bytes = shift;

    return  keys => 'type5', vals => 'vals5' ;


__DATA__
0C020B00C3A49999859583A807360000C5E4D9

输出

Bytes: 0C 02 0B 00 C3 A4 99 99 85 95 83 A8 07 36 00 00 C5 E4 D9

 Unpacked data
---------------
length_1 => 12
key      => 2
uid      => 11
char     => 0
data_1   => Currency
length_2 => 7
val      => 54
reserved => 0
data_2   => EUR

注意:

看起来可疑的是,val 只占用一个字节,而欧元的金额范围为 0..255。也许 reserved 字节可能是欧元 val 金额的一部分。

【讨论】:

以上是关于解包第一个字节表示长度的数据结构的主要内容,如果未能解决你的问题,请参考以下文章

如何从字节数组元素中解包/提取低阶和高阶值

Python 将二进制数据转换为 64 位浮点数。 “错误:解包需要 4 个字节的缓冲区”

数据库中日期型数据的长度是几个字节

如何将 4 字节的二进制数据解包为 3 字节和 1 字节的值?

有效地解包不同长度的数据帧列表

IP数据报 首部长度的60字节是怎么来的,下面的看不懂