我有原始的hexdump数据。如何将可变长度与许多不同的定界符进行匹配

时间:2019-01-10 10:25:14

标签: regex perl

Data

$dat[1] = "\x08\xB3\xE3\x0C\x09\x07\x4D\x6F\x68\x61\x6D\x65\x64\x1A";
$dat[2] = "\x08\x84\x03\x09\x03\x53\x6F\x6C\x6C\x1A";
$dat[3] = "\x08\xD4\xEA\x0E\x09\x03\x54\x6F\x6C\x1A";
$dat[4] = "\x08\xD5\x09\x03\x55\x6F\x6C\x1A";
$dat[5] = "\x08\xD4\xEA\x09\x09\x03\x54\x6F\x6C\x1A";
$dat[6] = "\x08\xD4\xEA\xOE\x09\x09\x54\x6F\x6C\x61\x6D\x65\x64\x61\x61\x1A";
$dat[7] = "\x08\xD4\xEA\x09\x09\x09\x54\x6F\x6C\x61\x6D\x65\x64\x61\x61\x1A";

我在上面的模式中有原始的hexdump数据。 08091A是分隔符。问题是列DF可能是09。是否可以匹配正则表达式?我需要这些定界符之间的数据。

我的代码不正确:

m/\x08(.+?\x09?)\x09(.+?)\x1A/s;

2 个答案:

答案 0 :(得分:4)

我假设记录格式定义如下:

  1. 每个记录由以类型(例如08091A)开头的字段组成。
  2. 字段类型1A是一种特殊类型,表示记录已结束。
  3. 所有记录的字段类型均为1A
  4. 字段类型08之后是使用this format编码的数字。
  5. 字段类型09后跟一个字节,该字节定义了字段其余部分中的字节数,该字节数似乎是ASCII编码的字符串。 (另一个合理的假设是,字段类型09之后是一个字节,该字节定义了随后使用UTF-8编码的代码点的数量。)
  6. 一条记录可能没有两个相同类型的字段。

我对以下内容不做任何假设:

  1. 是否必须存在类型08的字段。
  2. 是否必须存在类型09的字段。
  3. 字段顺序。

要解析此类记录,可以使用以下内容:

for ($file) {  # Makes $_ an alias for $file.
   REC: while (1) {
      my %rec;
      FIELD: while (1) {
         my $field_start = pos() || 0;
         if (!/\G ( . )/sxgc) {
            last REC if !%rec;
            die("Premature EOF\n");
         }

         if ($type eq "\x1A") {
            last;
         }

         elsif ($type eq "\x08") {
            !exists($rec{"09"})
               or warn(sprintf("Duplicate field of type %02X at pos %s\n", $type, $field_start));

            /\G ( [\x80-\xFF]*[\x00-\x7F] ) /sxgc
               or die(sprintf("Bad field of type %02X at pos %s\n", $type, $field_start));

            $rec{"08"} = unpack("w", "$1");
         }

         elsif ($type eq "\x09") {
            !exists($rec{"09"})
               or warn(sprintf("Duplicate field of type %02X at pos %s\n", $type, $field_start));

            /\G ( . ) /sxgc
               or die(sprintf("Bad field of type %02X at pos %s\n", $type, $field_start));

            my $len = ord($1);
            length() >= pos() + $len
               or die(sprintf("Bad field of type %02X at pos %s\n", $type, $field_start));

            $rec{"09"} = substr($_, pos(), $len);
            pos() += $len;
         }

         else {
            die(sprintf("Unrecognized record type %02X at pos %s\n", $type, $field_start));
         }
       }

      # Do something with %rec
   }
}

答案 1 :(得分:4)

似乎$dat[4]是无效数据。至少第一个字段应包含第二个字节,因为D5表示后面至少还有一个字节。

$dat[2]也是无效数据,因为0x09的长度字段为0x03,但该字段本身包含四个字符。

$dat[5]包含无效的十六进制转义。我使用\xEO而不是\xE0

通过这两个更正,您可以使用unpack函数来解析输入消息:

my( $number, $name ) = unpack 'xwxC/ax', $d;

unpack的模板表示:

x-丢弃此字节(0x08)

w-读取BER编码的数字

x-丢弃此字节(0x09)

C-读取此字节并将其用作以下字符串的长度

a-读取下一个字节并将其用作字符串字符

x-丢弃此字节(0x1A)

如果您还想保留字段号,请使用

    unpack 'CwCC/aC', $d

至少对于unpack模板中显示的数据,我已经假设了。如果这是实际的ASN.1数据,则应该进行更多的验证等操作,并且如果可能缺少字段分隔符,那么@ikegami所示的基于正则表达式的方法肯定更可靠。

固定/动态字段顺序

模板依赖于字段的固定顺序。如果不确定字段顺序是固定的,则需要根据循环中每个字段的类型确定unpack模板。这使解压缩方法接近池上的方法。

my ($message_type), $d = unpack 'CA*', $d;
if( $message_type eq "\x08" ) {
    my ($number), $d = unpack 'wA*', $d;
    print "Field 0x08: $number\n";
} elsif ...

有关固定字段顺序,请参见以下完整程序:

#!perl
use strict;
use warnings;

my @dat;

$dat[1] = "\x08\xB3\xE3\x0C\x09\x07\x4D\x6F\x68\x61\x6D\x65\x64\x1A";
#$dat[2] = "\x08\x84\x03\x09\x03\x53\x6F\x6C\x6C\x1A";
$dat[3] = "\x08\xD4\xEA\x0E\x09\x03\x54\x6F\x6C\x1A";
#$dat[4] = "\x08\xD5\x09\x03\x55\x6F\x6C\x1A";
$dat[5] = "\x08\xD4\xEA\x09\x09\x03\x54\x6F\x6C\x1A";
$dat[6] = "\x08\xD4\xEA\x0E\x09\x09\x54\x6F\x6C\x61\x6D\x65\x64\x61\x61\x1A";
$dat[7] = "\x08\xD4\xEA\x09\x09\x09\x54\x6F\x6C\x61\x6D\x65\x64\x61\x61\x1A";

@dat = grep {defined } @dat;

use Data::Dumper;

for my $d (@dat) {
    # Hardcoded message parser
    print Dumper [
        unpack 'CwCC/aC', $d
    ];

    # Dynamic message parser
    while( length $d ) {
        (my ($message_type), $d) = unpack 'aa*', $d;
        if( $message_type eq "\x08" ) {
            (my ($number), $d) = unpack 'wa*', $d;
            print "Field 0x08: $number\n";
        } elsif ( $message_type eq "\x09" ) {
            (my ($len)) = unpack 'C', $d;
            (my ($name), $d) = unpack 'C/aa*', $d;
            print "Field 0x09: $name\n";
        } elsif ( $message_type eq "\x1A" ) {
            # finished
            print "Field 0x1A\n";
        } else {
            die sprintf "Unknown message type %08x", ord($message_type);
        };
    };
};

输出

$VAR1 = [
          8,
          848268,
          9,
          'Mohamed',
          26
        ];
Field 0x08: 848268
Field 0x09: Mohamed
Field 0x1A
$VAR1 = [
          8,
          515,
          9,
          'Sol',
          26
        ];
Field 0x08: 515
Field 0x09: Sol
Field 0x1A
$VAR1 = [
          8,
          1389838,
          9,
          'Tol',
          26
        ];
Field 0x08: 1389838
Field 0x09: Tol
Field 0x1A
$VAR1 = [
          8,
          1389833,
          9,
          'Tol',
          26
        ];
Field 0x08: 1389833
Field 0x09: Tol
Field 0x1A
$VAR1 = [
          8,
          1389838,
          9,
          'Tolamedaa',
          26
        ];
Field 0x08: 1389838
Field 0x09: Tolamedaa
Field 0x1A
$VAR1 = [
          8,
          1389833,
          9,
          'Tolamedaa',
          26
        ];
Field 0x08: 1389833
Field 0x09: Tolamedaa
Field 0x1A

另请参见

unpack function

(un)pack parameters