解包第一个字节表示长度的数据结构
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提取length、key/tid、type
为这个特定的type
调用decoder子例程获取包含两个数组keys和vals
的哈希值用提供的 keys 和 vals
形成新的散列 %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 个字节的缓冲区”