81 · QR 码协议与编解码
对象:
base/settingsLibAndroid/src/{xcddif,f3dif}/java/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt(xcddif 版 362 行 / f3dif 版 387 行)。 常量板:同目录BluetoothBroadcastUtils.java。 本文行号一律为 xcddif 版(f3dif 为 SIG 格式,结构不同,见 §5)。
1. 两种前缀 = 两个协议版本
| flavor | 前缀(SCHEME_BT_BROADCAST_METADATA) | 协议 |
|---|---|---|
| xcddif (A14) | BT: | Android 14 AOSP 私有格式(AOSP 生态内互通) |
| f3dif (A17) | BLUETOOTH:UUID:184F; | 蓝牙 SIG Auracast 标准格式(0x184F = Broadcast Audio Announcement 服务 UUID) |
注意 f3dif 前缀自带尾分号——解析端 removePrefix 后直接进入 k:v 序列(Ext.kt :119 的写法在两 flavor 一致)。
2. 报文结构(xcddif 生成侧)
BT:R:65536;T:0;D:AA-BB-CC-DD-EE-FF;AS:12;B:34;BN:<b64>;PM:<b64>;SI:0;C:<b64>;SG:BS:…,BM:…,AC:<b64>;VN:U;;
└┬┘└───────────── 外层 k:v 用 ; 分隔 (:58) ─────────────┘ └─ SG 子组内用 , (:59) └┬┘└┬┘
前缀(:46) 后缀;;(:61)
字段表(键常量锚点 = xcddif Ext.kt):
| 键 | 行 | 含义 | 来源字段 | 出现 |
|---|---|---|---|---|
| R | :35 | QR 协议版本,固定 0x010000(:64) | 常量 | 必有 |
| T | :36 | 源地址类型 | sourceAddressType | 必有 |
| D | :37 | 源设备 MAC,: 替换为 - | sourceDevice.address | 必有 |
| AS | :38 | Advertising SID | sourceAdvertisingSid | 必有 |
| B | :39 | broadcastId | broadcastId | 必有 |
| BN | :40 | 广播名(UTF-8→Base64 NO_WRAP) | broadcastName | 可空才写 |
| PM | :41 | 公共广播元数据(rawMetadata→Base64) | publicBroadcastMetadata | 可空才写 |
| SI | :42 | PA sync 间隔 | paSyncInterval | 必有 |
| C | :43 | 加密 broadcastCode(Base64) | broadcastCode | 可空才写(有 C = 加密广播) |
| SG | :44 | 子组(可重复) | subgroups | 0..n |
| V | :45 | 厂商数据(可重复) | —(解析侧收进 vendorDataList 但不上 builder,:232-234 仅日志) | 0..n |
| VN | :46 | 生成端 Android 版本,固定 "U"(:63) | 常量 | 必有 |
子组内字段(, 分隔):BS bisSync 位图(:49) / BM bisMask 位图(:50) / AC 音频内容 rawMetadata→Base64(:51)。
厂商数据内字段:VI companyId(:54) / VD vendorData Base64(:55)。
二进制字段(BN/PM/C/AC/VD)统一 Base64.NO_WRAP(不留换行)。
3. 生成侧 toQrCodeString()(:76-103)
按固定顺序攒 entries → SCHEME + join(";") + ";;"。BN(:83-86)/PM(:87-90)/C(:92-95) 判空才加;subgroups 逐个转子串(:96-97);生成完 Log.d 打完整 QR 串(:101)——调试时 logcat 搜 TAG BtLeBroadcastMetadataExt 直接看到码内容。
4. 解析侧 convertToBroadcastMetadata()(:110-127)
- 前缀不匹配 → 打 error 日志返回 null(:111-115);
- strip 前缀+后缀 →
parseQrCodeToMetadata(:144); - 任何异常 →
Log.w+ null(:123-126)。调用方必须处理 null(扫码内容不是本协议时这是常态而非异常)。
parseQrCodeToMetadata 的防御性设计(值得借鉴):
- 逐字段查重:每个单值字段
require(还是初始值) { "Duplicate xxx" },重复键直接抛(如 :171/:175/:181); - 忽略第三段起:
split(":", limit = 2)只取前两段(:147/:168)——值里含:不炸; - 可重复键白名单:只有 SG(:217-219) 和 V(:221-223) 允许多次出现,进循环;
- 位图子集校验:bisSync 有偏好时必须是 bisMask 子集,否则 require 抛(:339-341);bisSync=0xFFFFFFFF 视为无偏好(:320/:338);
- 收尾建源设备:
BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(addr, type)(:235-237),MAC-还原为:(:182); - presentation delay 置 0 并注释原因(加入源时未知、无用,sink 同步后自知,:248-250)。
5. flavor 差异细节
- f3dif 版 387 行 vs xcddif 362 行:SIG 格式的字段体系与前缀不同,生成/解析逻辑相应不同。本文字段表只对 xcddif 成立;f3dif 逐字段对照留待补篇。
- 两 flavor 的
BluetoothBroadcastUtils.java除 :46 前缀外逐字节相同(diff 仅46c46,2026-09-06 核实)。
6. 维护注意
- 改协议字段 = 两 flavor 两份联动(xcddif + f3dif 的 Ext.kt;动前缀还要改各自 Utils:46)。dcd 无此文件不用管。
- 4 个零引用常量(TAG_FRAGMENT/ACTION/EXTRA×2)是上游同形遗物:删了无害但增加与 AOSP diff,留着无副作用;动它们前全仓 grep(含 XML 值)。
- 解析端异常吞掉返回 null 是设计行为,排障先看有没有
Cannot parse/does not begin with日志再怀疑调用方。
时效声明
行号 2026-09-06 于 dev 分支核对(主控逐行读 + 红队 agent 独立复核,见 _对抗核验记录-80篇.md)。引用前 grep -n 复核。