基于异或的简易校验和算法

157 阅读6分钟

基于异或的简易校验和算法

背景介绍

校验和 (Checksum) 是一种在数据通信和存储中广泛使用的错误检测技术。通过对一段数据进行计算得出一个固定长度的值(即校验和),可以用来验证数据在传输或存储过程中是否被意外篡改。如果接收方计算出的校验和与发送方提供的不一致,就说明数据可能已损坏。

本题要求你实现一种特定的、基于异或 (XOR) 运算的简易校验和算法。

核心概念

  1. 网络报文 (Packet):

    • 输入的数据是一段网络报文,以十六进制字符串的形式给出。
    • 2 个十六进制字符代表 1 个字节。例如,字符串 "61" 代表十六进制值 0x61
  2. 4字节块 (Word):

    • 本算法的核心操作单元是 4 字节(32位)的块。所有计算都将在这些块之间进行。

校验和计算规则

整个计算过程分为两个主要阶段:

第一阶段:数据补齐 (Padding)
  1. 首先,检查输入报文的字节长度是否为 4 的倍数。
  2. 如果不是,需要在报文的尾部用值为 0xFF 的字节进行补齐,直到总字节长度成为 4 的倍数。
  3. 最多补充 3 个字节。

示例:

  • 原始报文 "616263" (3字节),不是4的倍数,补齐 1 个 FF -> "616263FF"
  • 原始报文 "6162636465" (5字节),不是4的倍数,补齐 3 个 FF -> "6162636465FFFFFF"
第二阶段:异或折叠 (XOR Folding)
  1. 将补齐后的字节流,从头到尾分割成若干个 4 字节的块。设共有 n 个块。

  2. 如果 n = 1:

    • 那么这个唯一的 4 字节块本身就是最终的校验和。
  3. 如果 n > 1:

    • 取出第 1 个块和第 2 个块,对它们进行按位异或 (XOR) 运算。
    • 然后,将上一步的运算结果与第 3 个块继续进行异或运算。
    • 重复此过程,直到所有块都被异或完毕。最后得到的结果即为校验和。
    • 公式: Checksum = Block_1 XOR Block_2 XOR Block_3 ... XOR Block_n

解答要求

  • 时间限制: 1000ms
  • 内存限制: 256MB

输入格式

  • 一个十六进制字符串 expression

    • 1 <= expression.length <= 200,且长度为偶数。
    • 字符串仅由字符 0-9 和大写字母 A-F 组成。

输出格式

  • 一个十六进制字符串,表示最终计算出的校验和。

  • 格式要求:

    1. 字母使用大写 (A-F)。
    2. 固定为 8 个字符长度。如果计算结果不足 8 个十六进制字符(例如 "A9"),则需要在前面补充前导 0(例如 "000000A9")。

样例

输入样例 1

"61626364323031324C6162"

输出样例 1

"1F3330A9"

样例 1 解释:

  1. 原始报文: "61626364323031324C6162",字节长度为 11。

  2. 步骤 1 - 补齐:

    • 长度 11 不是 4 的倍数,需要补齐到 12 字节。
    • 补充 10xFF 字节。
    • 补齐后报文: "61626364323031324C6162FF"
  3. 步骤 2 - 异或计算:

    • 将报文分为 3 个 4字节块:

      • 块 1: 0x61626364
      • 块 2: 0x32303132
      • 块 3: 0x4C6162FF
    • 进行计算:

      • (块1 XOR 块2) = 0x61626364 XOR 0x32303132 = 0x53525256
      • (上一步结果 XOR 块3) = 0x53525256 XOR 0x4C6162FF = 0x1F3330A9
  4. 最终输出:

    • 计算结果为 0x1F3330A9,转换为 8 位大写十六进制字符串即为 "1F3330A9"

输入样例 2

"41"

输出样例 2

"41FFFFFF"

解释:

  • 输入报文长度为 1 字节。
  • 补齐到 4 字节,需要补充 3 个 0xFF 字节,得到 "41FFFFFF"
  • 此时只有 1 个 4字节块,它本身就是校验和。

输入样例 3

"687561776569"

输出样例 3

"0D1C9E88"

解释:

  • 输入长度 6 字节,补齐 2 个 FF -> "687561776569FFFF"
  • 分为两个块:0x687561770x6569FFFF
  • 0x68756177 XOR 0x6569FFFF = 0x0D1C9E88
  • 结果的十六进制表示是 "D1C9E88",长度为 7。根据输出要求,前面补充一个前导 0,变为 "0D1C9E88"
import java.util.Arrays;

/**
 * 实现一个简易的校验和(Checksum)算法.
 */
public class ChecksumCalculator {

    /**
     * 主方法,计算给定网络报文的校验和.
     * @param hexMessage 十六进制字符串表示的网络报文
     * @return 8位大写的十六进制字符串形式的校验和
     */
    public String calculateChecksum(String hexMessage) {
        // --- 步骤 1: 解析输入并进行填充 ---
        // 将十六进制字符串转换为字节数组
        byte[] originalBytes = hexStringToByteArray(hexMessage);
        
        // 计算需要补充的字节数
        int paddingCount = 0;
        if (originalBytes.length % 4 != 0) {
            paddingCount = 4 - (originalBytes.length % 4);
        }
        
        // 创建填充后的新字节数组
        byte[] paddedBytes = new byte[originalBytes.length + paddingCount];
        // 复制原始字节
        System.arraycopy(originalBytes, 0, paddedBytes, 0, originalBytes.length);
        // 填充 0xFF
        for (int i = 0; i < paddingCount; i++) {
            paddedBytes[originalBytes.length + i] = (byte) 0xFF;
        }

        // --- 步骤 2: 计算校验和 ---
        // 如果填充后只有一个4字节的块,那么它本身就是校验和
        if (paddedBytes.length == 4) {
            int checksum = bytesToInt(paddedBytes, 0);
            return formatChecksum(checksum);
        }

        // 如果有多个块,则进行累积异或运算
        // 初始化校验和为第一个4字节块的值
        int checksum = bytesToInt(paddedBytes, 0);
        
        // 从第二个4字节块开始,依次进行异或运算
        for (int i = 4; i < paddedBytes.length; i += 4) {
            int nextChunk = bytesToInt(paddedBytes, i);
            checksum ^= nextChunk; // checksum = checksum ^ nextChunk
        }

        // --- 步骤 3: 格式化并返回结果 ---
        return formatChecksum(checksum);
    }

    /**
     * 辅助方法:将十六进制字符串转换为字节数组.
     * @param s 待转换的十六进制字符串
     * @return 转换后的字节数组
     */
    private byte[] hexStringToByteArray(String s) {
        int len = s.length();
        // 一个字节由两个十六进制字符表示
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            // 将两个十六进制字符(如 "6A")合并成一个字节
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * 辅助方法:从字节数组的指定位置读取4个字节,并将其转换为一个32位整数 (int).
     * 采用大端序(Big-Endian),即高位字节在前。
     * @param bytes  源字节数组
     * @param offset 起始偏移量
     * @return 转换后的整数
     */
    private int bytesToInt(byte[] bytes, int offset) {
        // (bytes[i] & 0xFF) 是为了将有符号的 byte 转换为无符号的 int,防止符号位扩展带来的问题
        return ((bytes[offset]     & 0xFF) << 24) |
               ((bytes[offset + 1] & 0xFF) << 16) |
               ((bytes[offset + 2] & 0xFF) << 8)  |
               ((bytes[offset + 3] & 0xFF));
    }

    /**
     * 辅助方法:将一个32位整数格式化为8位长度、大写的十六进制字符串.
     * 一个16进制的字符表示半个字节,所以8位表示4个字节,正好是32位
     * @param checksum 待格式化的整数校验和
     * @return 格式化后的字符串
     */
    private String formatChecksum(int checksum) {
        // %08X 的含义:
        // X: 以大写十六进制格式输出
        // 8: 指定输出宽度为8位
        // 0: 如果不足8位,在前面用 '0' 补齐
        return String.format("%08X", checksum);
    }
}