计算机网络这门课提到过 CRC 检验。用 C# 模拟实现它主要关注模二除法和字节向整型数组的转换。

 

这里的实现方法效率很低,不过思想却很清晰。先把代码列出来:

 

    public class Buffoon_CRC_CCITT

    {

        private int[] Multinomial;

       

        public Buffoon_CRC_CCITT()

        {

            Multinomial = new int[17];

            for (int i = 0; i < 17; ++i)

            {

                if (i == 0 || i == 4 || i == 11 || i == 16) Multinomial[i] = 1;

                else Multinomial[i] = 0;

            }

        }

 

        public byte[] AddCRCCodes(byte[] bytes)

        {

            int[] source = ToArray(bytes);

            int[] codes = new int[source.Length + Multinomial.Length - 1];

 

            source.CopyTo(codes, 0);

 

            int startIndex = 0;

 

            for (; startIndex < source.Length; ++startIndex)

            {

                if (codes[startIndex] == 1) break;

            }

 

            for (int i = startIndex; i < source.Length; )

            {

                for (int j = 0; j < Multinomial.Length; ++j)

                {

                    codes[i + j] = (codes[i + j] + Multinomial[j]) % 2;

                }

 

                for (; i < source.Length; ++i)

                {

                    if (codes[i] == 1) break;

                }

            }

 

            source.CopyTo(codes, 0);

 

            return FromArray(codes);

        }

 

        public bool Verify(byte[] bytes, out byte[] source)

        {

            int[] codes = ToArray(bytes);

            int[] _source = new int[codes.Length - Multinomial.Length + 1];

 

            Array.Copy(codes, _source, _source.Length);

 

            int startIndex = 0;

 

            for (; startIndex < _source.Length; ++startIndex)

            {

                if (codes[startIndex] == 1) break;

            }

 

            for (int i = startIndex; i < _source.Length; )

            {

                for (int j = 0; j < Multinomial.Length; ++j)

                {

                    codes[i + j] = (codes[i + j] + Multinomial[j]) % 2;

                }

 

                for (; i < _source.Length; ++i)

                {

                    if (codes[i] == 1) break;

                }

            }

 

            for (int i = codes.Length - 1; i > codes.Length - Multinomial.Length + 1; --i)

            {

                if (codes[i] == 1)

                {

                    source = null;

                    return false;

                }

            }

 

            source = FromArray(_source);

            return true;

        }

 

        private int[] ToArray(byte[] b)

        {

            int[] arr = new int[b.Length * 8];

 

            for (int i = 0; i < b.Length; ++i)

            {

                arr[i * 8] = (b[i] & 0x80) >> 7;

                arr[i * 8 + 1] = (b[i] & 0x40) >> 6;

                arr[i * 8 + 2] = (b[i] & 0x20) >> 5;

                arr[i * 8 + 3] = (b[i] & 0x10) >> 4;

                arr[i * 8 + 4] = (b[i] & 0x08) >> 3;

                arr[i * 8 + 5] = (b[i] & 0x04) >> 2;

                arr[i * 8 + 6] = (b[i] & 0x02) >> 1;

                arr[i * 8 + 7] = b[i] & 0x01;

            }

 

            for (int i = 0; i < arr.Length; ++i)

            {

                Debug.Assert(arr[i] == 1 || arr[i] == 0);

            }

 

            return arr;

        }

 

        private byte[] FromArray(int[] arr)

        {

            Debug.Assert(arr.Length % 8 == 0);

 

            for (int i = 0; i < arr.Length; ++i)

            {

                Debug.Assert(arr[i] == 1 || arr[i] == 0);

            }

 

            byte[] bytes = new byte[arr.Length / 8];

 

            for (int i = 0; i < arr.Length; i += 8)

            {

                int value = 0;

                for (int j = 0; j < 8; ++j)

                {

                    if (arr[i + j] == 1) value += (int)Math.Pow((double)2, (double)(7 - j));

                }

 

                bytes[i / 8] = (byte)value;

            }

 

            return bytes;

        }

    }

 

As you can see, 我把这个类取名为“小丑_CRC_CCITT”。因为我觉得这个类干得事就是多此一举,没什么用,只会捣乱。不过这个练习对提高对二进制数的控制能力比较有用。

 

这个程序的思想就是把一个给定的字节数组转换成由 0, 1 组成的整型数组(这个整型数组用来表示二进制序列),然后对这个二进制数组进行运算。所以,一开始先看 FromArray() ToArray() 这两个函数。FromArray() 用来把二进制整型数组转换成字节数组。而 ToArray() 就是把字节数组转换成整型数组。

 

有这个两个函数垫底,我们就可以看给一个二进制流加上 CRC 校验码了。AddCRCCodes() 就是干这个事的。它用模二除法算出给定的字节数组对应的 CRC 校验码。

 

多项式用的是 CCITT 标准。即 多项式 = x^16 + x^12 + x^5 + 1。注意构造函数在初始化这个多项式的代码。我用多项式的 0 索引的元素表示最高位。

 

回过头来继续看 AddCRCCodes()。它把参数转换成整型数组,然后进行模二除法,算出余项。把余项附加到原来的字节数组上。最后把这个附加了余项的字节数组返回。由于 CCITT 的标准和 CRC-16 都是 16 次的多项式,故附加的余项长度正好为 2 个字节。注意,多项式的长度是 17 位,要比余项多一位。

 

知道如何加 CRC 余项后,校验的过程也就如法炮制了。它对应的函数为 Verify()

 

这个类在更改了构造函数对 Multinomial (多项式) 的初始化代码后,很容易地变成针对任意一个长度为 8 * n + 1 位多项式的 CRC 校验类。

 

当然老师验收这个题的时候,要我把结果用手算一遍,再和机器的输出比较。一样的话才算分。幸亏我当时写这个程序的时候注意了通用性。验收的时候把多项式改成 9 位的了。如果用手算 17 的多项式,那……