读卡器 HID 32/64 字节报文自动兼容设计

文档状态

背景与实测证据

两款读卡器的厂商、产品名和 VID/PID 相同,但 HID Report Descriptor 和 Android 可见的中断端点拓扑不同。终端 CPU 或 Android 系统的 32/64 位架构与 HID 报文长度无关。

项目 读卡器 A 读卡器 B
VID/PID 0610:2010 0610:2010
产品名 HID RF Picc Reader HID RF Picc Reader
序列号(仅用于实物记录) 0001A0000001 0001A0000000
描述符关键项 75 08 95 20 75 08 95 40
Input/Output Report 32/32 字节 64/64 字节
Interrupt IN 端点 1 个 1 个
Interrupt OUT 端点 0 个 1 个

75 08 表示 Report Size 为 8 位,95 2095 40 分别表示 Report Count 为 32 和 64。端点的 maxPacketSize=64 只是 USB 传输上限,不能单独用来判断实际 HID 报文长度。

已验证的平台限制与设计修订

最初设计拟通过 Android USB Host API 读取 HID Report Descriptor。目标 F700(Android 11)实测了三种读取时机和缓冲区策略:

  1. 占用 HID 接口后,以 4096 字节缓冲区读取。
  2. 从原始 Configuration Descriptor 取得声明长度后,按 33 字节精确读取。
  3. 打开设备后、占用 HID 接口前,按 33 字节精确读取。

三种标准 GET_DESCRIPTOR 请求均未返回 Report Descriptor。继续依赖该 API 会导致两款读卡器都无法连接,因此经确认改为使用 Android 能稳定枚举的中断端点拓扑。描述符解析试验代码已删除,避免保留不可达分支。

目标

  1. 同一套代码在每次连接时自动选择 32 或 64 字节 HID 报文。
  2. 初始化、寻卡、读卡、消费和协议通道关闭统一使用本次连接的配置。
  3. 报文长度判断不依赖终端系统位数、序列号或设备批次硬编码。
  4. 未知端点拓扑安全拒绝连接,不通过业务指令试探长度。
  5. 保持一卡通业务协议和交易结果未知保护不变。

非目标

选型规则

连接时只统计选中 HID 接口的 Interrupt 端点:

Interrupt IN 数量 Interrupt OUT 数量 连接级 Profile
1 0 Input 32 / Output 32
1 1 Input 64 / Output 64
其他 其他 UNSUPPORTED_HID_REPORT,拒绝连接

这是一项基于两款实物验证结果的协议族映射,集中在 HidReportProfileSelector 中。它不读取序列号、不判断终端位数,也不以 VID/PID 区分报文长度。若未来出现第三种拓扑,必须先取得协议和真机证据再扩展映射。

总体架构

HidReportProfile

连接级不可变值对象,分别保存 Input 和 Output Report 字节数。当前仅接受 32 或 64 字节。连接建立后到断开前不可切换,防止消费过程中改变协议外层长度。

HidReportProfileSelector

无 Android 依赖的纯 Java 组件,接收 Interrupt IN/OUT 数量并返回 profile。未知组合返回明确错误,不降级到默认长度。

AndroidUsbHidTransport

连接顺序为:

  1. 查找目标 VID/PID 并确认 USB 权限。
  2. 枚举 HID 接口,统计 Interrupt IN/OUT 端点并保存 IN 端点。
  3. 通过选择器生成连接级 profile;未知拓扑在打开和初始化协议前失败。
  4. 打开设备并占用接口,保存 profile。
  5. 收发缓冲区和长度校验全部使用 profile。
  6. 关闭连接时释放接口并清除 profile,重新插拔必须重新识别。

发送继续使用已验证的 CONTROL_SET_REPORT。64 字节读卡器虽然暴露 Interrupt OUT 端点,但端点在本次设计中只作为稳定的规格特征,不改变写入路径。

HidFrameCodecNewcapecProtocol

错误与交易安全

日志与可诊断性

每次连接记录:

端点 maxPacketSize 与 report 长度分别记录,避免现场把两者混为一谈。

测试与验收

自动化测试

真机验收

  1. 64 字节读卡器:自动选择 64/64,协议初始化成功,连续寻卡收发成功,退出正常断开。
  2. 32 字节读卡器:自动选择 32/32,协议初始化成功,连续寻卡收发成功,退出正常断开。
  3. 未放卡时允许返回 NO_CARD,但不得出现长度错误或通信超时。
  4. 真实消费需要测试卡、测试金额和单独授权,本次不执行。

设计结论

采用“Android USB 中断端点拓扑识别 + 不可变连接级 profile + 未知拓扑安全失败”的方案。同一套一卡通业务代码兼容已验证的 32 和 64 字节读卡器,不依赖终端位数或序列号,也不通过业务指令试探设备。