循环冗余检验算法(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;
}

posted @ 2022-03-15 01:01  zpchcbd  阅读(6971)  评论(0编辑  收藏  举报