十六位账本怎样发现传输差错:Internet Checksum 生活类比

Internet Checksum 把数据按 16 位分组,用反码加法折叠进位,再对结果取反。本文用"账本溢出回卷"类比解释算法,区分检错与纠错能力,并给出 Java 完整实现、奇数字节补零和两个已知测试向量。

先把十六位账本摊开

把每两个字节看成账本上的一格,每格最多记 16 位。普通加法溢出的进位若直接丢掉,前后两种数据可能更容易碰出同一结果;Internet Checksum 的做法是把溢出的 1 绕回最低位继续相加,最后把整本账逐位取反。接收方把数据和校验值按同样规则相加,若得到全 1,就说明这份账至少通过了本次检验。

溢出的进位绕回来

数据按网络字节序组成 16 位无符号字。所有字使用一补码加法累加:每当和超过 0xFFFF,就把高位进位加回低 16 位。处理完后再重复折叠可能残留的进位,最后按位取反并截为 16 位。奇数字节长度在计算时在末尾补一个零字节,但这个补位只是数学约定,不一定进入实际传输内容。算法能发现许多常见位翻转,却不是密码学摘要。

把主样例走一遍

测试向量由 0x0001、0xF203、0xF4F5、0xF6F7 四个字组成。普通和超过 16 位,把高位 2 回卷到低位后得到 0xDDF2,逐位取反为 0x220D。字符串 123456789 有九个字节,末尾的字符 9 与补零组成 0x3900,结果为 0xF62A。两个向量都写成断言,可同时检查字节序、进位折叠和奇数长度。

账目一致不等于不可伪造

循环处理到偏移 i 时,sum 等价于此前所有 16 位字的一补码和,且高位进位已经折叠。读取字节时必须先 & 0xFF,否则 Java 的 byte 符号扩展会污染高位。第二字节不存在时使用 0。最终 ~sum & 0xFFFF 明确把结果限制到无符号 16 位范围。

完整可运行代码

下面的程序只使用语言标准库,主函数直接包含可复制测试。Python 运行 python main.py,Java 使用 javac Main.java 后执行 java -ea Main,C++ 使用支持 C++17 的编译器。断言开启时,只要实现或预期结果不一致,进程就会以失败状态结束。

java 复制代码
import java.nio.charset.StandardCharsets;

public class Main {
    static int checksum(byte[] data) {
        long sum = 0;
        for (int i = 0; i < data.length; i += 2) {
            int high = data[i] & 0xFF;
            int low = (i + 1 < data.length) ? (data[i + 1] & 0xFF) : 0;
            sum += (high << 8) | low;
            sum = (sum & 0xFFFF) + (sum >>> 16);
        }
        while ((sum >>> 16) != 0) {
            sum = (sum & 0xFFFF) + (sum >>> 16);
        }
        return (int) (~sum) & 0xFFFF;
    }

    public static void main(String[] args) {
        byte[] vector = {0x00, 0x01, (byte)0xF2, 0x03,
                         (byte)0xF4, (byte)0xF5, (byte)0xF6, (byte)0xF7};
        assert checksum(vector) == 0x220D;
        assert checksum("123456789".getBytes(StandardCharsets.US_ASCII)) == 0xF62A;
        assert checksum(new byte[0]) == 0xFFFF;
        System.out.printf("0x%04X%n", checksum(vector));
    }
}

复杂度分析

设字节数为 n,每两个字节执行常数次操作,时间 O(n),额外空间 O(1)。算法适合边读边累加,不必复制报文。若数据分块到达,需要保留跨块的单个尾字节,不能分别给每块补零后再把校验值相加。

复杂度描述必须和代码的实际数据结构一致。只看最内层语句的常数时间,或把"通常很快"当成平均复杂度证明,都会误导选型。测试规模翻倍时应同时观察耗时和峰值内存,再判断渐进结论是否已经在当前数据范围显现。

边界条件

空数据的校验值为 0xFFFF;奇数字节需逻辑补零;字节序必须固定为网络序。Java byte 是有符号类型,拼字时必须掩码。若校验字段本身包含在报文中,发送端计算前要先把该字段置零,接收端验证时则连同收到的校验值一起计算,协议位置要说清楚。

边界不是正文末尾的附属清单,而是输入契约的一部分。调用方需要知道非法输入是抛异常、返回空结果还是给出状态码;同一项目中的测试、日志和监控也应使用相同语义。本文代码选择尽早失败,避免错误值继续流入后续计算。

常见错误

最常见错误是只保留低 16 位而丢弃进位,或只折叠一次却允许累加器积累多个高位。把 Checksum 描述成能保证数据未被篡改也不成立:不同报文可能碰撞,攻击者还能构造补偿变化。另一个误区是逐字节相加,这与协议规定的 16 位网络序分组不是同一算法。

排查时不要只打印最终答案。应记录首次破坏不变量的轮次、当前输入、辅助结构和刚执行的分支。这样错误能落到一个具体更新动作,而不是在整段代码里盲目改条件。对优化版本保留一个慢但直观的小规模基线,往往比增加更多手写样例更有效。

可复制的测试用例

代码断言 RFC 风格四字向量得到 0x220D,奇数字符串得到 0xF62A,并检查空数组。还应把任意偶数字节数据与其校验值拼接后验证全 1;奇数数据则要按协议字段位置处理补位,不能简单尾随两个字节。随机翻转单个位,统计被检出的比例可以帮助理解能力,但不能据此宣称绝对保证。

本地验证会从本文代码块提取源码,在独立临时目录编译或执行。测试结果只报告真实标准输出,不伪造时间数据。读者复制到自己的环境后,建议先保持相同断言,再替换业务输入;若接口有所调整,应同步修改异常约定和预期值。

从正确到可用

实现放进网络栈时,要明确硬件卸载是否已经计算校验和,否则抓包中看到的占位值可能被误判为故障。分片、重组和伪首部各有协议规则,不能只校验负载。指标应区分本地计算失败、接收校验失败和上游主动丢弃,避免把所有异常汇总成一个"网络错误"。

验收时再走一遍

验账时先把原始字节以十六进制两两分组,旁边写出每轮累加值、溢出高位和回卷后的低位,字节序错误会立刻显现。偶数字节向量验证正常分组,奇数字节向量验证逻辑补零,空输入确认约定值。再设计一个只翻转单个位的样例,检查接收端不再得到全 1;随后主动构造两处补偿变化,理解校验和存在碰撞而非误以为代码失效。分块输入测试要让块边界落在一个 16 位字中间,确认实现会保留尾字节。接入协议前把校验字段位置、伪首部范围与硬件卸载行为写入测试夹具,同一个函数只有在相同报文定义下才可比较。

什么时候该用,什么时候换方案

协议评审要把"发现偶然传输错误"和"抵抗主动篡改"分成两栏。前者可使用 Internet Checksum 并依赖现有协议字段,后者应选择带密钥的消息认证码或数字签名,不能靠扩大校验和位数冒充安全。若链路已经有更强的 CRC,仍要遵守上层协议是否要求本字段。计算函数、字段布局和验证流程分别测试,才能区分算法错误、序列化错误与抓包卸载假象。

针对本文的校验算法方案,评审记录还应同时写明输入所有权、异常返回、可重复性和资源上限。以主样例为起点,再加入最小输入、重复值、极端值和无解输入;每一项不仅保存期望输出,也保存推导理由。只有代码、复杂度说明与测试契约三者一致,才可以把一次演示视为可复用实现。

进一步上线前,应记录输入规模分布、失败类型、超时和资源峰值,而不是只统计成功请求的平均耗时。若实现含随机性,要保存种子;若依赖排序或哈希,要固定平局规则;若涉及数值累计,要为溢出和精度损失设置可观测指标。这样一次异常才能被复现,而不是被一句"偶发"带过。

总结

Internet Checksum 的价值在于便宜、可流式、容易由硬件实现,而不是不可碰撞。理解回卷进位、网络字节序和奇数补位后,代码很短;理解它的能力边界,才不会把检错工具误当成安全证明。

相关推荐
code2cat42 分钟前
Java进阶篇之AtomicMarkableReference:引用还在,标记已经改变
java·开发语言·cas·并发编程·逻辑删除
CappuccinoRose1 小时前
FormData数据处理
开发语言·前端·javascript·表单数据
Zguigo1 小时前
【CUDA8】第一个CUDA C++ Kernel --vector add
java·开发语言·c++
-Marks-1 小时前
【C++编程】STL容器(二)--- vector底层模拟实现(常用接口实现 | 扩容机制 | 深浅拷贝 | 迭代器失效)
开发语言·c++·vector·内存管理·迭代器失效·vector底层模拟实现
Shadow(⊙o⊙)2 小时前
C++11并发支持库
开发语言·c++
一技安身2 小时前
【java】JDK8 + JDK17 绿色解压共存方案-全程不修改任何系统环境变量
java·开发语言
hudd6683 小时前
JavaEE 初阶 - 多线程(初阶)
java·开发语言·java-ee
I Am a robert girl3 小时前
稀有事件估计的迭代去对齐:从源码视角拆解重要性采样新范式
开发语言·python·机器学习·重要性采样·蒙特卡洛方法·稀有事件估计
golang学习记3 小时前
rust开发,选VS Code还是RustRover?
开发语言·后端·rust
打工仔折腾 AI3 小时前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战