计算机网络这门课提到过 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位 的多项式,那……