d-bahr / CRCpp

Easy to use and fast C++ CRC library.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

HDLC/PPP CRC-16?

greyhare opened this issue · comments

I'm trying to use CRCpp to generate CRC-16s using the polynomial that PPP and HDLC uses. The code is from the minihdlc Github project.

  • Polynomial is either 0x8408 or 0x8810, depending on who is ordering the bits.
  • Initial value is 0xFFFF
  • Final CRC is XORed with 0xFFFF
  • Check value is 0xF0B8

The code they use (excerpted) is:

#define lo8(x)		((x)&0xff)
#define hi8(x)		((x)>>8)
/*
 Polynomial: x^16 + x^12 + x^5 + 1 (0x8408) Initial value: 0xffff
 This is the CRC used by PPP and IrDA.
 See RFC1171 (PPP protocol) and IrDA IrLAP 1.1
 */
static uint16_t _crc_ccitt_update(uint16_t crc, uint8_t data) {
	data ^= lo8(crc);
	data ^= data << 4;

	return ((((uint16_t) data << 8) | hi8(crc)) ^ (uint8_t) (data >> 4)
			^ ((uint16_t) data << 3));
}

(Though they forget to XOR at the end and they do "CRC the data and compare to the packet CRC" instead of "CRC the data and packet CRC and compare to the check value", their code can easily be tweaked to do the latter and gives what appears to be the correct answer.)

There's also a table-based implementation in Appendix A of RFC1549. I've implemented all three versions, using a 32-element test data vector of [ 0x60, 0x61, 0x62, ... 0x7E, 0x7F ] and I get a CRC of 0x75DF with CRCpp and 0x1214 with the other two versions. For CRCpp, I'm using these parameters:

static const CRCPP::CRC::Parameters<uint16_t, 16> parameters = { 0x8408, 0xFFFF, 0xFFFF, false, false };

Any ideas what's wrong? Also, can this polynomial be added to CRCpp?

My apologies for not getting to this sooner.

I think I've got it figured out. First, the input and output are reflected. This is not obvious at first, but makes sense, based on the implementation in the RFC. In a traditional CRC (one you might calculate by hand, for example), the remainder is calculated on the bits starting with the MSB (or whichever bit is transmitted first in a hardware implementation). However, you can tell from the implementation in the RFC that this is not happening, as they mask off the LSB when computing the lookup table, rather than the MSB. This is a rather unfortunate misnomer, but I see these kinds of bit ordering issues often.

The second thing, related to the first, is that when the data is reflected, the polynomial must also be reflected. So it becomes 0x1021.

The third thing that confused me for a while was that the "Good FCS" value in the RFC is just some placeholder value that has no meaning because they don't describe the input used to create that value. Whatever they used, it wasn't the canonical "123456789" string.

Thankfully, your example of [ 0x60, 0x61, 0x62, ... 0x7E, 0x7F ] gave me something useful to work with (thank you for that). These parameters produce a result of 0x1214 with that example input:

static const CRCPP::CRC::Parameters<uint16_t, 16> parameters = { 0x1021, 0xFFFF, 0xFFFF, true, true };

I'll go ahead and add the polynomial. Unless you think otherwise, I'll add it such that CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS will need to be defined to use it.

Huh, well, upon looking at the code a bit more, it looks like CRC++ already has it:

/**
    @brief Returns a set of parameters for CRC-16 X-25 (aka CRC-16 IBM-SDLC, CRC-16 ISO-HDLC, CRC-16 B).
    @note The parameters are static and are delayed-constructed to reduce memory footprint.
    @note CRC-16 X-25 has the following parameters and check value:
        - polynomial     = 0x1021
        - initial value  = 0xFFFF
        - final XOR      = 0xFFFF
        - reflect input  = true
        - reflect output = true
        - check value    = 0x906E
    @return CRC-16 X-25 parameters
*/
inline const CRC::Parameters<crcpp_uint16, 16> & CRC::CRC_16_X25()
{
    static const Parameters<crcpp_uint16, 16> parameters = { 0x1021, 0xFFFF, 0xFFFF, true, true };
    return parameters;
}

I'd give that CRC a try; let me know if it doesn't work for some reason.

OK, I'll check it. And I'm not sure it's that esoteric, since it's used in HDLC, which PPP is built on.

And "reflected means least significant bit first" wasn't immediately clear to me from the docs. The world's been Intelified for so long people seem to assume least-significant is the natural order. (As my friend says, "I'll start liking little endian when we start writing a thousand as 000,1.")

CRC-16-X25 does not require CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS. Hopefully that works :)

OK, finally got a chance to test it out, and it works, once I applied final XOR to my check value. However, I'm not sure why my check value (0x0F47 after final XOR) is so different from yours (0x906E). Same thing with CRC::CRC_32().

Hmm. Are you CRC-ing the data and packet CRC and comparing to the check value? If so, I wonder if the packet CRC is not in the same format (endianess, etc.) as the data, and that might mess it up. If you have an example packet, I can take a look.

I'm going to close this issue since it's been dormant for a few months. If you want to continue this discussion, feel free to reopen it.

Sorry, forgot this was still open.

Okay, I finally figured out what's going on. Sorry for all the confusion. I misunderstood what the check = value was in your CRC definitions.

I thought it was the final value of the CRC you expect to see if you process a block of data followed by its correct CRC. In a plain old CRC, this final value would be 0, since the CRC is the remainder, and by adding the remainder, the new remainder would be 0. However, when the initial value isn't 0 and the output is inverted, you get a constant instead that's related to the polynomial. For the CRC-8 I'm using, that value is 0x30; for CRC-16 (CRC::CRC_16_X25) it's 0xF47, and for CRC-32 (CRC::CRC_32) it's 0x2144DF1C. (All these CRCs are LSB-first, i.e. "reflected", xor their output, and have an all-ones initial vector. They're all from ISO/IEC 13239:2002 "High-level data link control (HDLC) procedures," section 4.2.5.)

But it finally dawned on me that the check value you're listing is the CRC you get from processing the nine ASCII bytes "123456789". Useful for unit testing, but a whole different animal.

FYI, the above CRC-8 isn't in CRCpp; it would have the following parameters:

    - polynomial     = 0x07
    - initial value  = 0xFF
    - final XOR      = 0xFF
    - reflect input  = true
    - reflect output = true
    - check value    = 0x2F

Glad you got it figured out.
I added CRC_8_HDLC to master (71f2152).