赞
踩
校验和(Checksum)参考文献RFC1071:https://www.rfc-editor.org/rfc/rfc1071.html
1)待计算校验和的数据的字节数如果偶数,后面不需要补字节;如果字节数是奇数,在后面补一字节(0x00)。(备注:补的全为0的这个字节仅仅是计算校验和使用,并不发送出去)
2)将每两个相邻的字节作为一组,组成一个16位的整数。
3)将组成的16位的整数系列计算1的补码和—就是先求二进制的和,然后再将超过最高有效位的进位(carries)加到结果的最低有效位上。
4)将上边计算的和取1的补码,即二进制中的1变0,0变1,结果就是最终的校验和。
5)校验和字段清零,存入上一步计算出来的校验。
1)如果接收到的数据(包含校验和字段)是偶数个字节,不需要考虑补一个字节的事情,直接跳到第3步。
2)如果接收到的数据(包含校验和字段)是奇数个字节,在校验和两个字节的前面插入一个字节(0x00),即在纯数据和校验和之间补一字节(0x00)。
3)每两个字节组成一个16位的整数,对这个整数系列计算1的补码和—就是先求二进制的和,然后再将超过最高有效位的进位(carries)加到结果的最低有效位上。
4)如果计算得到的结果各位全部是1(在1的补码中表示-0),那么就表示结果正确。
例如计算0xF6, 0xF7, 0x00, 0x01, 0xF4, 0xF5, 0xF2, 0x03这个字节序列的校验和。
因为要计算校验和的纯数据是偶数个字节,所以不需要考虑补1个字节的事情。
1) F6F7 + 0001 = F6F8
2) F6F8 + F4F5 = 1 EBED, 其中前面的1是超过最高有效位的进位
3) 1 EBED + F203 = 2 DDF0, 其中前面的2是超过最高有效位的进位
4) 将超过最高有效位的进位丢弃,然后将该丢弃的进位加到DDF0的最低有效位上,即DDF0 + 2 = DDF2
5)将DDF2求1的补码,即0变1,1变0,得到最终的校验和为220D,将这个填到校验和字段中。
6)最后发送出去的字节序列是(包含校验和):0xF6, 0xF7, 0x00, 0x01, 0xF4, 0xF5, 0xF2, 0x03, 0x22, 0x0D
此时要验证的字节系列为0xF6, 0xF7, 0x00, 0x01, 0xF4, 0xF5, 0xF2, 0x03, 0x22, 0x0D,其中后面的220D是发送端发送过来的校验和字段的值。
因为接收到的数据(包括校验和)是偶数个字节,所以不需要考虑补1个字节的事情。
1) F6F7 + 0001 = F6F8
2) F6F8 + F4F5 = 1 EBED, 其中前面的1是超过最高有效位的进位
3) 1 EBED + F203 = 2 DDF0 , 其中前面的2是超过最高有效位的进位
4)2 DDF0 + 220D= 2 FFFD, 其中前面的2是超过最高有效位的进位
5) 将超过最高有效位的进位丢弃,然后将该丢弃的进位加到FFFD的最低有效位上,即FFFD+ 2 = FFFF。结果各位全部为1,正确。
假设计算0xF6, 0xF7, 0x01这个字节系列的校验和。
1)因为要计算校验和的纯数据是奇数个字节,所以要在后面补一个字节0x00,补充后的字节序列是0xF6, 0xF7, 0x01, 0x00。
2)计算上面字节序列的1的补码和:F6F7 + 0100 = F7F7
3)将F7F7取1的补码,即0变1,1变0,得到最终的校验和为0x0808,将这个填到校验和字段中。
4)最后发送出去的字节序列是(包含校验和):0xF6, 0xF7, 0x01, 0x08, 0x08。
假设收到的要验证的字节系列为0xF6, 0xF7, 0x01, 0x08, 0x08,其中后面的0x0808是发送端发送过来的校验和字段的值。
1) 因为接收到的数据(包括校验和)是奇数个字节,所以要在纯数据和校验和之间补一个字节的0x00,补充后的字节序列是0xF6, 0xF7, 0x01, 0x00, 0x08, 0x08。
2) 计算上面字节序列的1的补码和:F6F7 + 0100 + 0808 = FFFF。结果各位全部为1,正确。
package com.thb; /** * 该类提供校验和的功能,计算校验和、检查校验和、计算1的补码和 * @author thb * */ public class ChecksumUtil { /** * 根据输入的字节数组数据计算校验和。校验和就是RFC1071描述的校验和 * @param sourceData 输入数据 * @return 16比特位的校验和 */ public static short calculateChecksum(byte[] sourceData) { short checksum; // 计算1的补码和 checksum = calculateOnesComplementSum(sourceData); // 将1的补码和取反,即0变1,1变0,就是校验和 checksum = (short)(~checksum); return checksum; } /** * 验证校验和 * 因为接收到的数据中包含了发送端填写的校验,所以接收端对接收到的数据计算 * 1的补码和,如果结果全为1,校验正确;否则,校验错误。 * @param receivedData 接收端收到的数据,其中最后两个字节是校验和 * @return true:检查成功, false:检查失败 */ public static boolean checkChecksum(byte[] receivedData) { short onesComplementSum; if ((receivedData.length % 2) == 0) { // 如果传入的数据是偶数个字节,那么直接计算1的补码和 onesComplementSum = calculateOnesComplementSum(receivedData); } else { // 如果传入的数据是奇数个字节,要在数据后面补一个字节0x00,后面再跟两个字节的校验和 // 构造一个数据数组,偶数个字节 byte[] data = new byte[receivedData.length + 1]; // 将原数据(除校验和两个字段外)先拷贝到data中 System.arraycopy(receivedData, 0, data, 0, receivedData.length - 2); // 数据后面最后一个字节填充0x00 data[data.length - 3] = (byte)0x00; // 将校验和两个字节拷贝到data中 System.arraycopy(receivedData, receivedData.length - 2 , data, data.length - 2, 2); // 计算1的补码和 onesComplementSum = calculateOnesComplementSum(data); } return onesComplementSum == (short)0xFFFF; } /** * 根据输入的字节数组数据计算16比特位的1的补码和(1's complement sum)。 * 就是将输入数据每两个字节组成一个16比特位的整数,然后将该整数系列二进制相加, * 并经超过最高有效位的进位丢弃,并经该丢弃的进位加到最低有效位上。 * @param data 输入数据 * @return 16比特位的1的补码和 */ public static short calculateOnesComplementSum(byte[] data) { // 考虑到进位,所以中间结果用32比特位的整数存放,高位两个字节存储的是进位 int middleSum = 0; for (int i = 0; i < data.length; i += 2) { if (data.length % 2 == 0) { // 输入数据的总字节数是偶数 // 将两个相邻的字节组合成一个整数,并和前面的整数和相加 middleSum += ((0xFF00 & (data[i] << 8)) | (0x00FF & data[i + 1])); } else { // 输入数据的总字节数是奇数 // 如果已经到了数据的最后一个字节,后面要补一个各bit位全为0的字节 if (i == (data.length - 1)) { middleSum += ((0xFF00 & (data[i] << 8)) | (0x00FF & 0x00)); } else { middleSum += ((0xFF00 & (data[i] << 8)) | (0x00FF & data[i + 1])); } } } // 定义进位变量 short carries; // 将进位取出来 carries = (short)((middleSum & 0xFFFF0000) >> 16); // 因为将进位加到后面两个字节上,可能又产生了进位,所以要用循环判断处理, // 直到不产生进位了为止 while (carries != 0) { // 将sum的前面两个字节清零,准备下面的计算 middleSum = (middleSum & 0x0000FFFF); // 将进位加到后面两个字节上 middleSum += carries; // 将进位再取出来,因为上面相加后可能又产生了进位 carries = (short)((middleSum & 0xFFFF0000) >> 16); } // 将上面计算结果的低端2个字节取出来返回,就是1的补码和 return (short)(middleSum & 0x0000FFFF); } }
package com.thb; public class Test2 { public static void main(String[] args) { // 发送端的原始数据 byte[] sourceData = new byte[] {(byte)0x01, (byte)0x06, 0x00, 0x06, (byte)0x37, (byte)0x02, (byte)0x23, (byte)0x23}; short checksum = ChecksumUtil.calculateChecksum(sourceData); System.out.println("checksum: 0x" + Integer.toHexString(Short.toUnsignedInt(checksum))); byte[] dstData = new byte[] {(byte)0x01, (byte)0x06, 0x00, 0x06, (byte)0x37, (byte)0x02, (byte)0x23, (byte)0x23, (byte)0xa4, (byte)0xce}; boolean result = ChecksumUtil.checkChecksum(dstData); System.out.println("verify checksum result: " + result); } }
运行输出:
checksum: 0xa4ce
verify checksum result: true
package com.thb; public class Test2 { public static void main(String[] args) { // 发送端的原始数据 byte[] sourceData = new byte[] {(byte)0x01, (byte)0x00, 0x00, 0x00, (byte)0x04, (byte)0x18, (byte)0x28, (byte)0x38, (byte)0x48}; short checksum = ChecksumUtil.calculateChecksum(sourceData); System.out.println("checksum: 0x" + Integer.toHexString(Short.toUnsignedInt(checksum))); byte[] dstData = new byte[] {(byte)0x01, (byte)0x00, 0x00, 0x00, (byte)0x04, (byte)0x18, (byte)0x28, (byte)0x38, (byte)0x48, (byte)0x8a, (byte)0xaf}; boolean result = ChecksumUtil.checkChecksum(dstData); System.out.println("verify checksum result: " + result); } }
运行输出:
checksum: 0x8aaf
verify checksum result: true
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。