循环冗余检验算法(CRC)与帧检验序列(FCS)
前言:CRC循环冗余校验和其中的FCS帧检验算法的学习笔记
需要明白的一点,CRC循环冗余校验是一种常用的检错方法,而FCS是添加在数据后面的用来校验的冗余码
后面的代码实现纯粹是个人的理解来实现的,所以自己觉得可能还是有部分BUG
什么是CRC循环冗余校验码
在计算机网络中的数据链路层传输数据时候,必须采用各种差错检测措施,为了保证数据传输的可靠性,这里可以可以通过循环冗余校验CRC来进行检验
其实不仅在计算机网络中,这种方法能够对传输的数据帧进行差错检验,那么只要是跟数据校验有关的都可以用到,比如逆向工程中的反调试手段,对于机器码的改变通过这种方法也能检验出来
CRC循环冗余校验码的原理
发送端,先把数据划分为组,假定每组k位比特,在每组M后面再添加供差错检测用的n位冗余码(帧检验序列FCS),然后构成一个帧发送出去,那么就是一共发送k+n位,如下图所示
在数据后面添加上的冗余码称为帧检验序列 FCS (Frame Check Sequence)。
接收端,接收到k+n位之后,将接收到都除以相同的P,然后检出余数R,如果余数R为0,则证明传输正确
冗余码FCS的计算
在数据后面添加上的冗余码称为帧检验序列 FCS (Frame Check Sequence)。
1、用二进制的模2运算进行2^n乘M的运算,这相当于在M后面添加n个0
2、得到的k+n位的数除以事先选定好的长度为n+1位的除数P,得出商是Q,余数是R,余数R比除数P少1位,即R是n位。
3、将余数R作为冗余码拼接在数据M后面,一起发送出去。
计算过程如下图所示
1、假定要传输的数据为M=101001(k=6);
2、假定除数为P=1101(因为选定的是除数P是4位,所以n=3,也就是P的位数-1)
3、在M后面加n个0(n=3,所以加3个0),得到M'=101001000;
4、用M'除以P
除数P是哪来的
对于CRC标准除数,一般使用多项式(或二项式)公式表示,如下图中除数11011(poly值为0x1b)的二项式为G(X)=X4+X3+X+1,X的指数就代表了该bit位上的数据为1,(最低位为0)。
这里特别注意一下位数问题,除数的位数为二项式最高次幂+1(4+1=5),这个很重要。
其他广泛使用的生成多项式P(X)如下所示
CRC16 = X^16 +X^15 + X^2 + 1
CRC-32 = X^32 +X^26 + X^23 + X^22 + X^16 +X^12 + X^11 +X^10 + X^8 +X^7+ X^5 +X^4 + X^2 + X + 1
代码实现
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<string.h>
#define BYTE_LENGTH 8
#define WORD_LENGTH 16
char divBinaryArray[WORD_LENGTH] = { 0 };
char bedivedBinaryArray[WORD_LENGTH] = { 0 };
uint32_t getBinaryLength(uint32_t aByte)
{
uint32_t numLength = 0;
uint32_t i = 0;
for (i = 0; i < WORD_LENGTH;)
{
if (aByte != 0)
{
numLength++;
aByte = aByte >> 1;
}
else
break;
}
return numLength;
}
void getBinaryArray(char* arrayBinary, uint16_t t, uint16_t index)
{
uint16_t u = 0;
if (t != 0)
{
u = t % 2; // 9 % 2 = 1
t /= 2; //
getBinaryArray(arrayBinary, t, index - 1);
arrayBinary[index] = u;
}
}
int main()
{
// 存放最终数据+冗余码
char* pAllocFCS = NULL;
char* p = NULL;
// 额外的变量
uint8_t count = 0;
uint8_t j;
// 临时指针
char* pTmp;
// 除数P, 0xD -> 1101 长度为4
uint32_t uintP = 0xD;
uint32_t divBinaryArrayLength = getBinaryLength(uintP);
// 被除数M, 0x29 -> 101001 长度为6
uint32_t uM = 0x29;
uint32_t bedivedBinaryArrayLength = getBinaryLength(uM);
// 字节数组binaryArray
getBinaryArray(divBinaryArray, uintP, divBinaryArrayLength - 1);
getBinaryArray(bedivedBinaryArray, uM, bedivedBinaryArrayLength - 1);
// 将M扩大2^n次
uM <<= (divBinaryArrayLength - 1);
// 加上除数P的长度-1 -> uN
// bedivedBinaryArrayLength += divBinaryArrayLength -1;
// 模拟发送端 -> 构造数据+冗余码
printf("=====模拟发送端 -> 构造数据+冗余码=====\n");
pTmp = bedivedBinaryArray;
while (1)
{
// ”== 3“的话写死了,因为这里用的是CRC3
if (bedivedBinaryArrayLength + divBinaryArrayLength - 1 + bedivedBinaryArray - pTmp == 3)
{
break;
}
if (*(char*)pTmp != 0)
{
printf("计算冗余码\n", count);
for (j = 0;j<4;j++)
{
pTmp[j] = pTmp[j] ^ divBinaryArray[j];
}
}
else
{
printf("位移\n");
pTmp++;
}
}
pAllocFCS = (char*)malloc(bedivedBinaryArrayLength + divBinaryArrayLength);
p = pAllocFCS;
memset(pAllocFCS, 0, bedivedBinaryArrayLength + divBinaryArrayLength - 1);
getBinaryArray(pAllocFCS, uM, getBinaryLength(uM)-1);
p += bedivedBinaryArrayLength;
for (j = 0; j<3; j++) // 3的话写死了,因为这里用的是CRC3的时候FCS就是三位
*p++ = *pTmp++;
// 模拟接收端 -> 检查数据的差错
printf("=====模拟接收端 -> 检查数据的差错=====\n");
pTmp = pAllocFCS;
while (1)
{
// ”== 3“的话写死了,因为这里用的是CRC3
if (bedivedBinaryArrayLength + divBinaryArrayLength - 1 + pAllocFCS - pTmp == 3)
{
break;
}
if (*(char*)pTmp != 0)
{
printf("计算冗余码\n", count);
for (j = 0; j<4; j++)
{
pTmp[j] = pTmp[j] ^ divBinaryArray[j];
}
}
else
{
printf("位移\n");
pTmp++;
}
}
// 释放内存
if (pAllocFCS != NULL)
{
free(pAllocFCS);
pAllocFCS = NULL;
}
return 0;
}