阅读 384

crc32校验在Javascript中的实现

用途

在数据传输过程中,无论传输系统的设计再怎么完美,差错总会存在,这种差错可能会导致在链路上传输的一个或者多个帧被破坏(出现比特差错,0变为1,或者1变为0),从而接受方接收到错误的数据。为尽量提高接受方收到数据的正确率,在接收方接收数据之前需要对数据进行差错检测,当且仅当检测的结果为正确时接收方才真正收下数据。检测的方式有多种,常见的有奇偶校验、因特网校验和循环冗余校验等。循环冗余校验是一种用于校验通信链路上数字传输准确性的计算方法(通过某种数学运算来建立数据位和校验位的约定关系的

CRC无处不在:著名的通讯协议X.25的FCS(帧检错序列)采用的是CRC-CCITT,WinRAR、NERO、ARJ、LHA等压缩工具软件采用的是CRC32,磁盘驱动器的读写采用了CRC16,通用的图像存储格式GIF、TIFF等也都用CRC作为检错手段

原理

image.png

假设A想传递一个数据0到B,由于外界的干扰信号有可能会变成1 而这个时候B无法判断信息到底传递的对不对。

先采用一个最简单的方法来描述检测,如果A想要传递一个0 那就连续发送三个0。当B接收到数据的时候,如果信号受到干扰,比如说B接收到的信息是010,那么B可以发现传递过来的数据并不一致。说明信号存在干扰,且大概率A想要传递的信息是0

但是也有可能存在B接收到111,所以错误检查并不能100%保证数据是正确的,只是通过某种数学模型将这个概率降低到极小

crc循环冗余校验同其他差错检测方式一样,通过在要传输的k比特数据D后添加(n-k)比特冗余位

可以这么简单理解:上述三个000中 第一位为信息位 后面两个00位校验位

crc的数学模型非常的复杂(有兴趣的可以自行查找资料),是基于某个多项式计算出一组校验码,用于核对数据传输过程中是否被更改或传输错误

一般来说crc的计算涉及到如下概念

  • 参数模型 ---- 标准中的模型
  • 多项式值 ---- 生成项的简写,以16进制表示
  • 初始值 ---- 这是算法开始时寄存器(crc)的初始化预置值,十六进制表示
  • 结果异或值 ---- 计算结果与此参数异或后得到最终的CRC值

计算示例

假设原数据为 10110011

参数模型:crc-4

  1. 多项式:x^4+x^3+1 对应的2进制码 2^4+2^3+1=25 -->11001
  2. 因为校验码4位,所以10110011后面需加4个0,得到101100110000,用“模2除法” (即逻辑亦或^) 即可得出结果

  1. 即CRC^101100110000得到101100110100,并发送到接收端。
  2. 接收端收到101100110100后除以11001(以“模2除法”方式去除),余数为0则无差错

一般来说CRC有多种实现方式,直接生成法 和 查表法 。

直接生成法 适用于 CRC 次幂较小的格式,当CRC 次幂逐渐增高时,因为其复杂的Xor 逻辑运算会拖累系统运行速度,不再建议使用直接生成法,取而代之的是查表法——将数据块M 的一部分提前运算好,并将结果存入数组中,系统开始执行运算时,相当于省去了之前的操作,直接从类似中间的位置开始计算,所以会提高效率。

在计算CRC时也可以选择正向校验(Normal) 或者反向校验(Reversed),由于 Normal 和 Reversed 是镜像关系,两种方法会影响到最后的校验码,使得两种方式最后得到的校验码呈现镜像关系。 但这对CRC 本身的成功率并没有影响,只不过是: 正向走一遍,还是镜像走一遍罢了。

表的建立方法:

如果寄存器的首位为1,将寄存器右移1位(将Mx^r剩下部分的MSB移入寄存器的MSB(高八位)),再与G(x) 的后r位异或,否则仅将寄存器右移1位(将Mx^r剩下部分的LSB(低八位)移入寄存器的LSB)

计算crc校验码的方法(查表法)

  1. 将上一次crc与新的要校验的字节进行XOR 异或运算;
  2. 用运算出的值在预先生成码表中进行索引,获取对应的值(称为余式);
  3. 用获取的值与crc右移后的值进行XOR 异或运算;
  4. 如果要校验的数据已经处理完,则第(4)步的结果就是最终的CRC校验码。如果还有数据要进行处理,则再转到第(1)步运行

背景知识

我们在讨论crc的实现之前先要了解一下关于Javacript中二进制数据的知识 首先要了解的就是ArrayBuffer,以下是Mdn上关于它的定义

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。

换个通俗一点的说法 ArrayBuffer 是一个对象,可以分配一段用于存放数据的连续内存区域

new ArrayBuffer(256)
复制代码

你不能直接操作 ArrayBuffer 的内容,而是要通过类型数组对象DataView 对象来操作

所谓的类型数组对象 指的是👇 这些

Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();
复制代码

Javascript代码 CRC-32-IEEE 802.3 标准

例如: CRC-32-IEEE 802.3采用的多项式

0xEDB88320 反向校验🐴

初始值 0xff

// 查表法 预先构建一张表
// 在CRC-16和32中,一次移出的待测数据为 8 位 bits,即一次进行一个字节的计算,
// 则表格有 2^8=256 个表值。一个字节有8位二进制数,每一位都有2种选择。
let table: Uint32Array = new Uint32Array(256);
for (var i = 0; i < 256; i++) {
    var c = i;
    for (var k = 0; k < 8; k++) { 
        c = 
            (c & 1) ? // LSM为1
            (c >>> 1) ^ 0xEDB88320   //采取反向校验
            : c >>> 1; //对应 上文 否则仅将寄存器右移1位
    }
    table[i] = c;
}

function crc32(bytes:Uint8Array, start: number, length: number) {
    start = start || 0;
    length = length || (bytes.length - start);
    // 上面是用来取需要检验的位置
    var crc = -1; 
    for (var i = start, l = start + length; i < l; i++) {
        crc = (crc >>> 8) ^ table[(crc ^ bytes[i]) & 0xFF] ;
    }
    return crc ^ (-1);

}
复制代码

参考文章

www.cnblogs.com/masonzhang/…
bbs.pediy.com/thread-1719…

文章分类
前端
文章标签