循环冗余检验算法(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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY