如何使用Perl的'Digest::CRC'模块计算DNP3 CRC

How to use Perl's 'Digest::CRC' module to calculate DNP3 CRC

我正在尝试计算 DNP3 的 link 级别的 CRC。规范说它是基于多项式 X^16 + X^13 + X^12 + X^11 + X^10 + X^8 + X^6 + X^5 + X^2 + 1 的 2 个八位字节 CRC。它也被反转并首先放入数据块 LSB。

使用 Perl 的摘要包,我将算法设置为:

my $ctx = Digest::CRC->new(width=>16, init=>0, xorout=>0xffff, refin=>1, refout=>1, poly=>0x3d65, cont=>0xea82);

$ctx->add(0x05);
$ctx->add(0x64);
$ctx->add(0x05);
$ctx->add(0xF2);
$ctx->add(0x01);
$ctx->add(0x00);
$ctx->add(0x00);
$ctx->add(0x00);

my $x=$ctx->digest;
printf("x=%04x\n",$x);

规范中为 header 显示的示例: 05 64 05 F2 01 00 00 00 校验和应该是 52 摄氏度。我得到 x=91fc.

我试过所有参数但似乎无法让它出来。有什么建议么?我需要逐字节添加()数据吗?逐字(2 字节)?

您的数据与来自 http://reveng.sourceforge.net/crc-catalogue/16.htm

CRC-16/DNP 兼容
width=16  
poly=0x3d65  
init=0x0000  
refin=true  
refout=true 
xorout=0xffff  
check=0xea82  
name="CRC-16/DNP"

您可以使用 https://www.lammertbies.nl/comm/info/crc-calculation.html

来验证这一点

(此问题交叉发布在 PerlMonks - 参见 here. The answer below is also cross posted at PerlMonks - see here

我想我会挑战自己,看看我是否能解决这个问题。当我开始研究 CRC 算法时,我很快发现自己不知所措。但我认为我实际上能够弄清楚。

查看Digest::CRC的源代码,我猜测'const'的值应该是0而不是'0xea82'。这让我更接近 ASCII 输入。结合 Anonymous Monk 的建议让我更接近十六进制输入。在这两种情况下,我所说的更接近的意思是我得到了正确的字符,但顺序错误。基本上,我需要按位反转输出。例如,对于您的示例 header,我得到的是 'c52' 而不是“520c”。这里的两位是“0c”和“52”。反转 'c52'(或“0c52”)的两位产生“520c”。

在搜索 CRC-DNP 校验和的参考资料时,我找到了一个在线校验和计算器(找到 here - 并在 gammatester 的回答中引用)。我用它来'verify'下面代码的输出。

use strict;
use warnings;

use feature 'say';

use Digest::CRC;

sub Left_Pad {
    my $value = shift;

    if (length($value) % 2) {$value = '0'.$value;}
    return $value;
}

sub Bitwise_Reverse {
    my $value = shift;

    $value = Left_Pad($value);
    my $offset = length($value);
    my $reversed;
    while ($offset > 0) {
        $offset -= 2;
        my $string = substr($value,$offset,2);
        $reversed .= $string;
    }
    return $reversed;
}

sub CRC_DNP_ASCII {
    my $value = shift;

    my $ctx = Digest::CRC->new(width=>16, init=>0x0, xorout=>0xffff, refin=>1,
                               refout=>1, poly=>0x3d65, cont=>0);
    $ctx->add($value);
    my $digest = $ctx->hexdigest;
    my $crc = Bitwise_Reverse($digest);
    return $crc;
}

sub CRC_DNP_HEX {
    my $value = shift;

    my $ctx = Digest::CRC->new(width=>16, init=>0x0, xorout=>0xffff, refin=>1,
                               refout=>1, poly=>0x3d65, cont=>0);
    my $offset = 0;
    while ($offset < length($value)) {
        my $string = substr($value,$offset,2);

        $ctx->add(chr(hex($string)));
        $offset += 2;
    }
    my $digest = $ctx->hexdigest;
    my $crc = Bitwise_Reverse($digest);
    return $crc;
}

my @data_list = ('056405F201000000','56405F201000000');
foreach my $data (@data_list) {
    say "Calculating CRC-DNP checksum for '$data':";
    my $ascii = CRC_DNP_ASCII($data);
    say "    ASCII input:  $ascii";
    my $hex = CRC_DNP_HEX($data);
    say "      Hex input:  $hex\n";
}

产生了以下输出:

Calculating CRC-DNP checksum for '056405F201000000':
    ASCII input:  99fc
      Hex input:  520c

Calculating CRC-DNP checksum for '56405F201000000':
    ASCII input:  0751
      Hex input:  11e3

我已经尝试了一些其他示例来比较我的代码与在线计算器的输出,并且我得到了相同的 CRC-DNP 校验和值。

也许更有知识的人能够发现我的代码的问题,但我认为它应该可以帮助您进一步了解您正在尝试做的事情。

好的,在你们的帮助下,侵入 Digest::CRC 并逐步比较 IEEE Std 1815 中 C 样本 CRC 计算给出的结果,我想出了使用以下代码:

my $ctx = Digest::CRC->new(width=>16, init=>0, xorout=>0xffff, refin=>1, refout=>1, poly=>0x3d65, cont=>0);

$ctx->add(chr(0x05));
$ctx->add(chr(0x64));
$ctx->add(chr(0x05));
$ctx->add(chr(0xF2));
$ctx->add(chr(0x01));
$ctx->add(chr(0x00));
$ctx->add(chr(0x00));
$ctx->add(chr(0x00));

my $x=$ctx->digest;
printf("x=%02X %02X\n",$x>>8, $x&0xFF);

我的原始代码的问题不在 CRC 摘要中,而是我将数据传递到模块的方式。我将它作为单个标量值传入。 Digest::CRC 然后会使用以下代码将其附加到数据缓冲区:

$self->{_data} .= join '', @_ if @_;

这(显然)将标量转换为字符串(即 0x05 为“5”)。随之而来的是使用代码访问第一个字节的摘要子例程:

ord(substr($message, $pos++, 1))

这导致消化值 0x35,而不是我预期的 0x05。

解决方案是通过将数据包装为字符串(即 chr(0x05))将数据传递到模块中。

谢谢大家,尤其是 gammatester 和 dasgar,为我指明了正确的方向。