2

I'm having real problems trying to implement a XOR8/LRC checksum in PHP, according to the algorithm present here: http://en.wikipedia.org/wiki/Longitudinal_redundancy_check

What I'm trying to do is, given any string calculate its LRC checksum.

For example, I know for sure this string:

D$1I 11/14/2006 18:15:00 1634146 3772376 3772344 3772312 3772294 1*

Has a hexadecimal checksum of 39 (including the last * char).

For anyone interested what is the meaning of the string, it's is a DART (Deep-ocean Assesment and Reporting of Tsunamis) message - http://nctr.pmel.noaa.gov/Dart/Pdf/dartMsgManual3.01.pdf.

I convert the string to a binary string with 1's and 0's. From there, I try to create a byte array and apply the algorithm to the byte array, but it's not working and I can't figure out why.

The function I'm using for converting to String to Binary String is:

        function str2binStr($str) { 
         $ret = '';
         for ($i = 0, $n = strlen($str); $i < $n; ++$i) 
           $ret .= str_pad(decbin(ord($str[$i])), 8, 0, STR_PAD_LEFT); 
         return $ret; 
        }

The function I'm using for converting from Binary String to Binary Array is:

        function byteStr2byteArray($s) {
          return array_slice(unpack("C*", "\0".$s), 1);
        }

Finally, the LRC implementation I'm using, with bitwise operators, is:

        function lrc($byteArr) {
           $lrc = 0;
           $byteArrLen = count($byteArr);
           for ($i = 0; $i < $byteArrLen; $i++) {
             $lrc = ($lrc + $byteArr[$i]) & 0xFF;
           }
           $lrc = (($lrc ^ 0xFF) + 1) & 0xFF;
           return $lrc;
        }

Then, we convert the final decimal result of the LRC checksum with dechex($checksum + 0), so we have the final hexadecimal checksum.

After all these operations, I'm not getting the expected result, so any help will be highly appreciated.

Thanks in advance.


Also, I can't make it work following the CRC8-Check in PHP answer.

Community
  • 1
  • 1
Pablo Valls
  • 21
  • 1
  • 3

3 Answers3

1

I'm afraid that nobody on StackOverflow can help you, and here's why. This question was bugging me so I went to the DART website you mentionned to take a look at their specs. Two problems became apparent:

  • The first one is you have misunderstood part of their specs. Messages start with a Carriage Return (\r or \0x0D) and the asterisk * is not part of the checksum

  • The second, bigger problem is that their specs contain several errors. Some of them may originate from bad copy/paste and/or an incorrect transformation from Microsoft .doc to PDF.

I have taken the time to inspect some of them so that would be nice if you could contact the specs authors or maintainers so they can fix them or clarify them. Here is what I've found.

2.1.2 The message breakdown mentions C/I as message status even though it doesn't appear in the example message.

2.1.3 The checksum is wrong, off by 0x31 which corresponds to the character 1.

2.2.3 The six checksums are wrong, off by 0x2D which corresponds to the character -.

2.3.1.2 I think there's a <cr> missing between dev3 and tries

2.3.1.3 The checksum is off by 0x0D and there's no delimiter between dev3 and tries. The checksum would be correct if there was a carriage return between the dev3 value and the tries value.

2.3.2.2-3 Same as 2.3.1.2-3.

2.3.3.3 Wrong checksum again, and there's no delimiter before tries.

2.4.2 The message breakdown mentions D$2 = message ID which should be D$3 = message ID.

Here's the code I used to verify their checksums:

$messages = array(
    "\rD\$0 11/15/2006 13:05:28 3214.2972 N 12041.3991 W* 46",
    "\rD\$1I 11/14/2006 18:15:00 1634146 3772376 3772344 3772313 3772294 1* 39",
    "\rD\$1I 11/14/2006 19:15:00 1634146 3772275 3772262 3772251 3772249 1* 38",
    "\rD\$1I 11/14/2006 20:15:00 1634146 3772249 3772257 3772271 3772293 1* 3E",
    "\rD\$1I 11/14/2006 21:15:00 1634146 3772315 3772341 3772373 3772407 1* 39",
    "\rD\$1I 11/14/2006 22:15:00 1634146 3772440 3772472 3772506 3772540 1* 3C",
    "\rD\$1I 11/14/2006 23:15:00 1634146 3772572 3772603 3772631 3772657 1* 3B",
    "\rD\$2I 00 tt 18:32:45 ts 18:32:00 3772311\r00000063006201* 22",
    "\rD\$2I 01 tt 18:32:45 ts 18:32:00 3772311\r000000630062706900600061005f005ffffafff9fff8fff8fff7fff6fff401* 21",
    "\rD\$2I 02 tt 18:32:45 ts 18:32:00 3772335\rfffdfffafff7fff5fff1ffeeffea00190048ffe1ffddffdaffd8ffd5ffd101* 21"
);

foreach ($messages as $k => $message)
{
    $pos = strpos($message, '*');

    $payload = substr($message, 0, $pos);
    $crc = trim(substr($message, $pos + 1));

    $checksum = 0;

    foreach (str_split($payload, 1) as $c)
    {
        $checksum ^= ord($c);
    }

    $crc = hexdec($crc);

    printf(
        "Expected: %02X - Computed: %02X - Difference: %02X - Possibly missing: %s\n",
        $crc, $checksum, $crc ^ $checksum, addcslashes(chr($crc ^ $checksum), "\r")
    );
}
Josh Davis
  • 28,400
  • 5
  • 52
  • 67
  • Oh wow, amazing answer, I'll split it into parts. About including the * char or not, in the document it says you must not include it (all chars preceding *), but after answering them about the checksums they told me the * should be included in the checksum, so I took this last approach. I just completely missed the carriage returns for the examples, thanks for pointing that out. I know about the 2.1.2 message breakdown lack of C/I, and it's because the status can be C/I/NS (NS = Not Set); this is present in a side-document they sent me by email. Be sure this fixing notes will arrive to.. – Pablo Valls Jun 09 '11 at 16:01
  • ..them, and I will link directly to his stackoverflow question. Thanks a lot again for the incredible help. – Pablo Valls Jun 09 '11 at 16:02
0

I realize that this question pretty old, but I had trouble figuring out how to do this. It's working now, so I figured I should paste the code. In my case, the checksum needs to return as an ASCII string.

public function getLrc($string)
{
    $LRC = 0;
    // Get hex checksum.
    foreach (str_split($string, 1) as $char) {
        $LRC ^= ord($char);
    }
    $hex = dechex($LRC);
    // convert hex to string
    $str = '';
    for($i=0;$i<strlen($hex);$i+=2) $str .= chr(hexdec(substr($hex,$i,2)));
    return $str;
}
Charlie
  • 498
  • 2
  • 10
  • 24
0

For what it's worth, here's a completely unoptimized, straight-up implementation of the algorithm from Wikipedia:

$buffer = 'D$1I 11/14/2006 18:15:00 1634146 3772376 3772344 3772312 3772294 1*';

$LRC = 0;
foreach (str_split($buffer, 1) as $b)
{
    $LRC = ($LRC + ord($b)) & 0xFF;
}
$LRC = (($LRC ^ 0xFF) + 1) & 0xFF;

echo dechex($LRC);

It results in 0x0E for the string from your example, so either I've managed to fudge the implementation or the algorithm that produced 0x39 is not the same.

Josh Davis
  • 28,400
  • 5
  • 52
  • 67
  • Hi Josh, thanks a lot for your time. The implementation says: "Exclusive OR of all characters, including '*', so I assume it's a XOR8 checksum. Also, why are you splitting the string into an array, and then processing each char into it's ASCII value? Are you sure that's what a byte means in the algorithm of wikipedia? – Pablo Valls Jun 09 '11 at 15:37
  • In PHP, strings are an array of bytes. In fact, you could just XOR each character against the next, e.g. `ord("a" ^ "b")` is the same as `ord("a") ^ ord("b")`. – Josh Davis Jun 09 '11 at 15:47
  • Thanks for the clarification. Combining this with your other answer, everything seems to be much more clear. – Pablo Valls Jun 09 '11 at 16:07