22

I am trying to generate a CRC-16 using C#. The hardware I am using for RS232 requires the input string to be HEX. The screenshot below shows the correct conversion, For a test, I need 8000 to be 0xC061, however the C# method that generates CRC-16 must be able to convert any given HEX string.

Screenshot of required output.

I have tried using Nito.KitchenSink.CRC

I have also tried the below which generates 8009 when 8000 is inputted -

public string CalcCRC16(string strInput)
    {
        ushort crc = 0x0000;
        byte[] data = GetBytesFromHexString(strInput);
        for (int i = 0; i < data.Length; i++)
        {
            crc ^= (ushort)(data[i] << 8);
            for (int j = 0; j < 8; j++)
            {
                if ((crc & 0x8000) > 0)
                    crc = (ushort)((crc << 1) ^ 0x8005);
                else
                    crc <<= 1;
            }
        }
        return crc.ToString("X4");
    }

    public Byte[] GetBytesFromHexString(string strInput)
    {
        Byte[] bytArOutput = new Byte[] { };
        if (!string.IsNullOrEmpty(strInput) && strInput.Length % 2 == 0)
        {
            SoapHexBinary hexBinary = null;
            try
            {
                hexBinary = SoapHexBinary.Parse(strInput);
                if (hexBinary != null)
                {
                    bytArOutput = hexBinary.Value;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        return bytArOutput;
    }
dynamicuser
  • 1,522
  • 3
  • 24
  • 52
  • Alright, so there's some code to generate a CRC (I'm assuming). What's the specific problem/question? – Chris Sinclair Apr 04 '14 at 10:36
  • The problem is the generated CRC is incorrect usign the code above. I will update the question with its current output. – dynamicuser Apr 04 '14 at 10:37
  • 2
    The input "8000" - is that decimal 8000, hex 8000, or a string "8000" ? – Marc Gravell Apr 04 '14 at 10:41
  • The device requires it to be HEX – dynamicuser Apr 04 '14 at 10:41
  • 2
    @MattBaughan no, that isn't the question; computers don't actually talk in hex or decimal or anything else. They just have numbers. What I need to know is: what do **you** (not the machine) mean by 8000? CRC runs on bytes. I need to know what byte sequence we are hashing. I can interpret 8000 as about 15 byte sequences without even trying. I need to know which. – Marc Gravell Apr 04 '14 at 10:43
  • Sorry, if you look at the screenshot, the HEX radio button is checked. I then input 8000 and the generated CRC that I require is 0xC061. The 8000 is a string to start with. – dynamicuser Apr 04 '14 at 10:44
  • I will be always hashing a string – dynamicuser Apr 04 '14 at 10:51
  • @MattBaughan no, you will always be hashing bytes. Hashes work on bytes, not strings. To get to bytes, you need to know the encoding rules. Is this ASCII? i.e. "8000" => {56, 48, 48, 48} ? – Marc Gravell Apr 04 '14 at 10:54
  • For info, the CRC-16 algo that I posted (now deleted) is the CRC-CCITT (XModem) algo, aka CRC16 ISO 13239. I will see if I can find which algo produces the other result – Marc Gravell Apr 04 '14 at 11:00
  • All I know is the device sends HEX values as a string via RS-232 and I pick them up. I already have a method which converts a string to byte[] and I send messages back to the device using a method which returns a HEX string. The device understands this and all works fine, I simply need to implement CRC checking on strings being sent from device to PC. Cheers – dynamicuser Apr 04 '14 at 11:01

2 Answers2

42

Here we go; note that this is a specific flavor of CRC-16 - it is confusing to say just "CRC-16". This borrows some implementation specifics from http://www.sanity-free.com/ - note I have made it static rather than instance-based.

using System;

static class Program
{
    static void Main()
    {
        string input = "8000";
        var bytes = HexToBytes(input);
        string hex = Crc16.ComputeChecksum(bytes).ToString("x2");
        Console.WriteLine(hex); //c061
    }
    static byte[] HexToBytes(string input)
    {
        byte[] result = new byte[input.Length / 2];
        for(int i = 0; i < result.Length; i++)
        {
            result[i] = Convert.ToByte(input.Substring(2 * i, 2), 16);
        }
        return result;
    }

    public static class Crc16
    {
        const ushort polynomial = 0xA001;
        static readonly ushort[] table = new ushort[256];

        public static ushort ComputeChecksum(byte[] bytes)
        {
            ushort crc = 0;
            for (int i = 0; i < bytes.Length; ++i)
            {
                byte index = (byte)(crc ^ bytes[i]);
                crc = (ushort)((crc >> 8) ^ table[index]);
            }
            return crc;
        }

        static Crc16()
        {
            ushort value;
            ushort temp;
            for (ushort i = 0; i < table.Length; ++i)
            {
                value = 0;
                temp = i;
                for (byte j = 0; j < 8; ++j)
                {
                    if (((value ^ temp) & 0x0001) != 0)
                    {
                        value = (ushort)((value >> 1) ^ polynomial);
                    }
                    else
                    {
                        value >>= 1;
                    }
                    temp >>= 1;
                }
                table[i] = value;
            }
        }
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    Hi, this is not working for me, this line `result[i] = Convert.ToByte(input.Substring(2 * i, 2), 16);` the second param is expecting a `System.IFormatProvider` value, of which 16 is apparently not one of them. – Shaun Bebbers Mar 27 '17 at 17:08
  • @Shaun method definitely exists in full framework: https://msdn.microsoft.com/en-us/library/c7xhf79k(v=vs.110).aspx - are you perhaps using .net core? Or another reduced framework? – Marc Gravell Mar 27 '17 at 17:25
  • 1
    Seems to compute wrong values. At least they don't match the ones I get from the CRC16 function on Atmel AVR. Looking for another source now. – ygoe Dec 30 '19 at 15:44
  • 4
    @ygoe I deliberately started the post by highlighting that there are multiple algorithms known as CRC16 which are incompatible; knowing which you mean is very important. Quite likely, we're referring to different ones here. – Marc Gravell Dec 30 '19 at 18:06
12

In Addition, If you want CRC16-CCITT.

private ushort Crc16Ccitt(byte[] bytes)
{
    const ushort poly = 4129;
    ushort[] table = new ushort[256];
    ushort initialValue = 0xffff;
    ushort temp, a;
    ushort crc = initialValue;
    for (int i = 0; i < table.Length; ++i)
    {
        temp = 0;
        a = (ushort)(i << 8);
        for (int j = 0; j < 8; ++j)
        {
            if (((temp ^ a) & 0x8000) != 0)
                temp = (ushort)((temp << 1) ^ poly);
            else
                temp <<= 1;
            a <<= 1;
        }
        table[i] = temp;
    }
    for (int i = 0; i < bytes.Length; ++i)
    {
        crc = (ushort)((crc << 8) ^ table[((crc >> 8) ^ (0xff & bytes[i]))]);
    }
    return crc;
}
Tur1ng
  • 745
  • 8
  • 22
TheDebugger
  • 149
  • 2
  • 9
  • 1
    `Crc16Ccitt(Encoding.ASCII.GetBytes("123456789")).ToString("X")` returns `29B1`. According to this [webpage](http://srecord.sourceforge.net/crc16-ccitt.html) the implementation is incorrect. – Denxorz Oct 22 '19 at 07:21
  • 3
    Changing `initialValue` to `0x0x1D0F` seems to work. – Denxorz Oct 22 '19 at 07:47