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

时间:2016-09-06 20:27:07

标签: perl crc

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

使用Perl的Digest软件包,我将算法设置为:

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);

标题显示的规范中的示例: 05 64 05 F2 01 00 00 00 校验和应该是 52℃我得到x = 91fc。

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

3 个答案:

答案 0 :(得分:1)

您的数据与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

进行验证

答案 1 :(得分:1)

(此问题在PerlMonks上发布 - 请参阅here。以下答案也在PerlMonks上发布 - 请参阅here

我以为我会挑战自己,看看能否解决这个问题。当我开始研究CRC算法时,我很快就发现了自己。但我认为我实际上能够弄明白。

查看Digest :: CRC的源代码,我猜测'const'的值应为0而不是'0xea82'。这让我更接近ASCII输入。将其与Anonymous Monk的建议结合起来让我更接近十六进制输入。在这两种情况下,接近我的意思是我得到了正确的角色,但顺序错误。基本上,我需要按位反转输出。例如,使用您的示例标题,我得到'c52'而不是'520c'。这里的两位是'0c'和'52'。反转'c52'(或'0c52')的两位会产生'520c'。

在搜索CRC-DNP校验和的引用时,我找到了一个在线校验和计算器(找到here - 并在gammatester的答案中引用)。我用它来“验证”以下代码的输出。

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校验和值相同。

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

答案 2 :(得分:0)

好的,在你们大家的帮助下,攻击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,指出我正确的方向。