如何使用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,为我指明了正确的方向。
我正在尝试计算 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,为我指明了正确的方向。