当你用USB分析仪捕获到一串Token包数据时,是否好奇过那个神秘的5位CRC校验码是如何生成的?本文将带你用Python代码亲手验证这个隐藏在USB协议中的数学魔术,从抓包数据到算法实现,完整还原CRC-5的计算过程。
USB协议中的Token包就像交通指挥灯,控制着数据传输的方向和时序。常见的IN、OUT、SETUP等Token包都包含两个关键字段:
典型的Token包结构如下表所示:
| 字段 | 位数 | 示例值(二进制) |
|---|---|---|
| PID | 4 | 1011 (OUT Token) |
| ADDR | 7 | 0000101 (0x05) |
| ENDP | 4 | 0100 (0x04) |
| CRC | 5 | 10000 (0x10) |
注意:USB采用LSB-first(低位优先)传输方式,实际传输时每个字节的比特顺序是反转的
CRC-5使用的生成多项式为:
code复制G(x) = x⁵ + x² + 1 (二进制表示为100101)
我们需要以下工具来验证CRC-5:
首先安装必要的Python包:
bash复制pip install pyusb pyserial
创建一个新的Python文件usb_crc5.py,导入基础模块:
python复制def print_binary(data, width=8):
"""格式化输出二进制"""
return format(data, f'0{width}b')
以抓包数据为例(ADDR=0x05, ENDP=0x04, CRC=0x10),我们逐步拆解计算过程:
准备原始数据:
0000101 + 0100 = 00001010100位序反转(因USB采用LSB-first):
0000101010000101010000附加5个0(CRC-5需要):
00101010000 + 00000 = 0010101000000000模2除法计算:
code复制0010101000000000
^ 100101
---------
1100100000000
^100101
---------
101110000000
^100101
---------
01011000000
000000
---------
1011000000
^100101
---------
010010000
000000
---------
10010000
^100101
---------
00001000
000000
---------
0001000
00000
---------
001000
00000
---------
01000
0000
---------
1000
^100101
---------
00101 (余数)
最终处理:
以下是完整的Python实现代码:
python复制def usb_crc5(data, width=5):
"""计算USB CRC-5校验码"""
poly = 0b100101 # x⁵ + x² + 1
crc = 0b11111 # 初始值为全1
# 处理每个数据位
for byte in data:
crc ^= byte
for _ in range(8):
if crc & (1 << (width+1)):
crc = (crc << 1) ^ poly
else:
crc <<= 1
crc &= 0b11111 # 保留5位
# 最终处理
crc ^= 0b11111
return crc & 0b11111
def build_token_packet(addr, endp):
"""构建Token包数据"""
# 7位地址 + 4位端点号
data = ((addr & 0x7F) << 4) | (endp & 0x0F)
# 转换为字节数组(注意USB的LSB-first特性)
return bytes([data & 0xFF, (data >> 8) & 0xFF])
# 示例:验证抓包数据
addr, endp = 0x05, 0x04
packet = build_token_packet(addr, endp)
crc = usb_crc5(packet)
print(f"ADDR: 0x{addr:02X}, ENDP: 0x{endp:01X} → CRC-5: 0x{crc:02X}")
运行结果应该显示:
code复制ADDR: 0x05, ENDP: 0x04 → CRC-5: 0x10
使用Wireshark捕获USB数据包时,可以按以下步骤提取Token包:
usb.token将抓取到的数据输入我们的Python脚本进行验证。例如捕获到:
code复制OUT Token
Device Address: 0x12
Endpoint Number: 0x3
CRC: 0x0D
验证代码:
python复制addr, endp = 0x12, 0x03
packet = build_token_packet(addr, endp)
crc = usb_crc5(packet)
assert crc == 0x0D, "CRC验证失败"
print("CRC验证通过!")
当你的计算结果与抓包数据不符时,检查以下方面:
位序问题:
多项式选择:
初始值设置:
最终处理步骤:
调试时可以添加打印语句观察中间过程:
python复制def debug_crc5(data):
poly = 0b100101
crc = 0b11111
print(f"初始值: {print_binary(crc, 5)}")
for byte in data:
print(f"\n处理字节: {print_binary(byte)}")
crc ^= byte
print(f"异或后: {print_binary(crc, 8)}")
for i in range(8):
if crc & 0x20:
crc = (crc << 1) ^ poly
else:
crc <<= 1
crc &= 0b11111
print(f"位 {i}: {print_binary(crc, 5)}")
crc ^= 0b11111
print(f"最终CRC: {print_binary(crc, 5)}")
return crc
理解了基本原理后,我们可以进一步优化代码:
python复制# 预计算CRC表
crc5_table = [usb_crc5(bytes([i])) for i in range(256)]
def fast_crc5(data):
crc = 0b11111
for byte in data:
crc = crc5_table[(crc ^ byte) & 0xFF]
return crc ^ 0b11111
python复制def calculate_crc(data, poly, init, final_xor, ref_in, ref_out, width):
crc = init
for byte in data:
if ref_in:
byte = int('{:08b}'.format(byte)[::-1], 2)
crc ^= byte << (width - 8) if width > 8 else byte
for _ in range(8):
if crc & (1 << (width - 1)):
crc = (crc << 1) ^ poly
else:
crc <<= 1
crc &= (1 << width) - 1
if ref_out:
crc = int('{:0{width}b}'.format(crc, width=width)[::-1], 2)
return crc ^ final_xor
c复制#include <stdint.h>
uint8_t usb_crc5(uint8_t *data, uint8_t len) {
uint8_t crc = 0x1F; // 初始值
uint8_t poly = 0x25; // 多项式(00100101)
for(uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for(uint8_t j = 0; j < 8; j++) {
if(crc & 0x80) {
crc = (crc << 1) ^ poly;
} else {
crc <<= 1;
}
}
}
return (crc >> 3) ^ 0x1F; // 取高5位并取反
}
通过这个实践项目,我们不仅理解了USB协议中CRC校验的底层原理,还掌握了将协议规范转化为实际代码的能力。下次当你看到USB分析仪中的CRC字段时,不再是一个黑盒数字,而是一段可以亲手验证的数学之美。