wdtj wdtj - 2 months ago 17
Perl Question

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

I'm trying to calculate a CRC for DNP3's link level. The spec says it's a 2 octet CRC based on the polynomial X^16 + X^13 + X^12 + X^11 + X^10 + X^8 + X^6 + X^5 + X^2 + 1. It's also inverted and put in the data block LSB first.

With Perl's Digest package, I set up the algorithm as:

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


The example in the spec shown for the header:
05 64 05 F2 01 00 00 00
the checksum should be
52 0C. I get x=91fc.

I've tried playing with all the parameters but can't seem to get it to come out. Any suggestions? Do I need to add() the data byte by byte? word (2 byte) by word?

Answer

OK, with a little help from you folks, and hacking into the Digest::CRC and comparing, step-by-step the results given by the C Sample CRC calculations in the IEEE Std 1815, I came up with the following code:

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

The problem with my original code wasn't in the CRC digest, but the way I was passing the data into the module. I passed it in as a single scalar value. Digest::CRC would then append this to the data buffer with the code:

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

This (apparently) converted the scalar to a string (i.e. 0x05 to a "5"). Along comes the digest subroutine that access the first byte with the code:

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

This resulted in digesting the value 0x35, not 0x05 as I had intended.

The solution was to pass the data into the module by wrapping the data as a string (i.e. chr(0x05)).

Thank you all, especially gammatester and dasgar, for pointing me in the right direction.