0

I have this block of C code that I can not for the life of me understand. I need to calculate the CRC-16 for a certain byte array I send to the method and it should give me the msb(most significant byte) and the lsb(least significant byte). I was also given a C written app to test some functionality and that app also gives me a log of what is sent and what is received via COM port.

What is weird is that I entered the hex string that I found in the log into this online calculator, but it gives me a different result.

I took a stab at translating the method to C#, but I don't understand certain aspects:

  1. What is pucPTR doing there (it's not beeing used anywhere else)?
  2. What do the 2 lines of code mean, under the first for?
  3. Why in the second for the short "i" is <=7, shouldn't it be <=8?
  4. Last line in if statement means that usCRC is in fact ushort 8005?

Here is the block of code:

unsigned short CalculateCRC(unsigned char* a_szBufuer, short a_sBufferLen)
{
    unsigned short usCRC = 0;
    for (short j = 0; j < a_sBufferLen; j++)
    {
        unsigned char* pucPtr = (unsigned char*)&usCRC;
        *(pucPtr + 1) = *(pucPtr + 1) ^ *a_szBufuer++;
        for (short i = 0; i <= 7; i++)
        {
            if (usCRC & ((short)0x8000))
            {
                usCRC = usCRC << 1;
                usCRC = usCRC ^ ((ushort)0x8005);
            }
            else
                usCRC = usCRC << 1;
        }
    }
    return (usCRC);
}

This is the hex string that I convert to byte array and send to the method: 02 00 04 a0 00 01 01 03

This is the result that should be given from the CRC calculus: 06 35

The document I have been given says that this is a CRC16 IBM (msb, lsb) of the entire data.

Can anyone please help? I've been stuck on it for a while now.

Any code guru out there capable of translating that C method to C#? Apparently I'm not capable of such sourcery.

Garf365
  • 3,619
  • 5
  • 29
  • 41
Alex123
  • 173
  • 2
  • 2
  • 13
  • 2
    Just to note: `*(pucPtr + 1)` is a alternate way of writing `pucPtr[1]`. – user694733 Feb 01 '17 at 10:17
  • Thank you @user694733, that is good information. So that means that // unsigned char* pucPtr = (unsigned char*)&usCRC // actually means that usCRC is converted to byte and added to byte[] pucPTR ? – Alex123 Feb 01 '17 at 10:25
  • 1
    1+2: `pucPtr` is used to access high byte of `usCRC` (to XOR it with current byte in the `a_szBufuer`); 3: `<=7` will run cycle 7+1 times, you mix it up with `<8` (which is absolutely identical, but may produce different ASM/IL) 4: Nope, it's [XOR](https://en.wikipedia.org/wiki/Bitwise_operations_in_C#Bitwise_XOR_.22.5E.22) again. Search for "crc 16 c#" to get find working C# code instead of transforming someone's C implementation of it, [here](http://stackoverflow.com/a/22861111/1997232) is something (you just have to change polinominal to `0x8005`). – Sinatr Feb 01 '17 at 10:30
  • @Sinatr sorry misread something, you are right. Comment deleted. – Jabberwocky Feb 01 '17 at 10:34
  • 1
    One important thing to note here: Using `pucPtr` to access second byte of `usCRC` is not portable solution: It will give different results on little vs big endian systems. You need to know for which architecture this code was originally written for. – user694733 Feb 01 '17 at 10:39
  • 1
    Number one is probably to find a code guru who can give you a proper algorithm in C. This code is badly written. – Lundin Feb 01 '17 at 10:58

2 Answers2

5

First of all, please note than in C, the ^ operator means bitwise XOR.

  1. What is pucPTR doing there (it's not beeing used anywhere else)?
  2. What do the 2 lines of code mean, under the first for?

Causing bugs, by the looks of it. It is only used to grab one of the two bytes of the FCS, but the code is written in an endianess-dependent way.

Endianess is very important when dealing with checksum algorithms, since they were originally designed for hardware shift registers, that require MSB first, aka big endian. In addition, CRC often means data communication, and data communication means possibly different endianess between the sender, the protocol and the receiver.

I would guess that this code was written for little endian machines only and the intent is to XOR with the ms byte. The code points to the first byte then uses +1 pointer arithmetic to get to the second byte. Corrected code should be something like:

uint8_t puc = (unsigned int)usCRC >> 8;
puc ^= *a_szBufuer;
usCRC = (usCRC & 0xFF) | ((unsigned int)puc << 8);
a_szBufuer++;

The casts to unsigned int are there to portably prevent mishaps with implicit integer promotion.


  1. Why in the second for the short "i" is <=7, shouldn't it be <=8?

I think it is correct, but more readably it could have been written as i < 8.

  1. Last line in if statement means that usCRC is in fact ushort 8005?

No, it means to XOR your FCS with the polynomial 0x8005. See this.

The document I have been given says that this is a CRC16 IBM

Yeah it is sometimes called that. Though from what I recall, "CRC16 IBM" also involves some bit inversion of the final result(?). I'd double check that.


Overall, be careful with this code. Whoever wrote it didn't have much of a clue about endianess, integer signedness and implicit type promotions. It is amateur-level code. You should be able to find safer, portable professional versions of the same CRC algorithm on the net.


Very good reading about the topic is A Painless Guide To CRC.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Wow thank you for all the help and information, but my lack of knowledge makes me ask you this question: The puc is an unsigned 8 bit integer which in C# is a byte, any other integer type in C# is 16bit and above. The compiler will not let me convert a ushort (16bit integer) to a byte (8 bit integer). How could that be accomplished? – Alex123 Feb 01 '17 at 11:14
2
  1. What is pucPTR doing there (it's not beeing used anywhere else)?

pucPtr is used to transform uglily an unsigned short to an array of 2 unsigned char. According endianess of platform, pucPtr will point on first byte of unsigned short and pucPtr+1 will point on second byte of unsigned short (or vice versa). You have to know if this algorithm is designed for little or big endian.

Code equivalent (and portable, if code have been developed for big endian):

unsigned char rawCrc[2];
rawCrc[0] = (unsigned char)(usCRC & 0x00FF);
rawCrc[1] = (unsigned char)((usCRC >> 8) & 0x00FF);

rawCrc[1] = rawCrc[1] ^ *a_szBufuer++;

usCRC = (unsigned short)rawCrc[0] 
       | (unsigned short)((unsigned int)rawCrc[1] << 8);

For little endian, you have to inverse raw[0] and raw[1]

  1. What do the 2 lines of code mean, under the first for?

First line does the ugly transformation described in 1.

Second line retrieves value pointed by a_szBufuer and increment it. And does a "xor" with second (or first, according endianess) byte of crc (note *(pucPtr +1) is equivalent of pucPtr[1]) and stores results inside second (or first, according endianess) byte of crc.

*(pucPtr + 1) = *(pucPtr + 1) ^ *a_szBufuer++;

is equivalent to

pucPtr[1] = pucPtr[1] ^ *a_szBufuer++;
  1. Why in the second for the short "i" is <=7, shouldn't it be <=8?

You have to do 8 iterations, from 0 to 7. You can change condition to i = 0; i<8 or i=1; i<=8

  1. Last line in if statement means that usCRC is in fact ushort 8005?

No, it doesn't. It means that usCRC is now equal to usCRC XOR 0x8005. ^ is XOR bitwise operation (also called or-exclusive). Example:

 0b1100110
^0b1001011
----------
 0b0101101
Garf365
  • 3,619
  • 5
  • 29
  • 41
  • Be aware that `rawCrc[1] << 8` causes undefined behavior on 8/16 bit computers, because of implicit integer promotion. You end up shifting data into the sign bits of a temporary `int`. – Lundin Feb 01 '17 at 11:00
  • @Lundin as each time, you see what's wrong ;) thank you for remark! – Garf365 Feb 01 '17 at 11:02