前言: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;
}