先交代两句背景方便第一次进系列的朋友跟上。蓝牙经典音频设备里控制器固件住着一个叫链路管理器的模块两台设备连上之后两边各自的链路管理器就开始隔空对话你是谁、支持什么特性、要不要加密、语音链路怎么排时隙、怎么省电。这套对话用的语言就是链路管理协议消息叫 PDU。这些 PDU 不走普通数据通道而是塞进控制专用的逻辑链路里随数据包过江。前面十一篇把加密握手、嗅探协商、语音建链、测试模式这些流程挨个过了一遍这篇换个姿势不追流程改读花名册——规范最后压轴的 PDU 总表88 条指令每条叫什么、多长、坐什么车、谁能发、带什么参数全在上面。这张表平时没人正眼看其实它是链路管理层的账本前面所有流程的指令都能在这里查到户口。目录一、账本怎么读五列各管一摊二、操作码的空间经济学127 是一扇逃生门三、坐什么车DM1 与 DV 的捎带学问四、方向列的门道谁发起是设计过的五、全家福点名88 条指令按家族走一遍六、Note 1 深潜eSCO 的还价艺术七、把账本装进代码一个最小解析器八、拿抓包分析常客、过客与三出戏九、收个尾十、测验一、账本怎么读五列各管一摊每条 PDU 在表里占一行五个维度。Length 是整条消息的字节数注意包含操作码自己——LMP_ACCEPTED 长 2 字节是一个操作码加一个回显的操作码LMP_DETACH 也是 2 字节操作码加错误码。Opcode 是身份证号。Packet type 规定这条消息允许坐哪种包。Possible direction 约束谁有资格发起C 是中央、P 是外设也就是微微网里的主从两端。最后的 Contents 列逐字节给出参数在载荷里的位置写固件的人照着切就行。表头只有短短一句话但信息量不小原文如下Where the Possible direction column in Table 5.1 shows B, the PDU is sent on the APB-C logical link. All other PDUs are sent on the ACL-C logical link and only in the direction(s) indicated.方向列标了 B 的走的是广播物理链路上的控制逻辑链路其余全部走常规的 ACL-C 控制链路且只按标注的方向收发。整张表里方向为 B 的只有一条——LMP_CLK_ADJ时钟调整指令。为什么它要广播因为时钟调整带着生效时刻参数微微网里所有设备必须在同一个瞬间步调一致地动时钟点对点发一遍根本保证不了原子性。这条孤零零的 B背后是广播等时业务对全网同步的硬需求。对着截图核一遍开头几行账本的格式就熟了LMP_ACCEPTED 两字节、操作码 3、DM1/DV 双向可捎带LMP_AU_RAND 十七字节随机数、只能专车往下到 LMP_CLK_ADJ十五字节、127 转义、方向一栏写着孤零零的 B。二、操作码的空间经济学127 是一扇逃生门操作码只有一个字节理论容量 256。表里实际用的是 1 到 126 加上一个特殊的 127。设计这套编码的年代大概觉得一百多个够用了结果生态爆发嗅探子分级、安全简单配对、增强速率协商、信道分类、槽可用性修改……新需求一波接一波直码很快见底。规范没有推倒重来而是把 127 做成了转义符第一个字节写 127第二个字节写扩展操作码从第三字节起才是真正的指令编号。表里的写法如 127/01 表示转义加扩展码 1实际就是 LMP_ACCEPTED_EXT。这套设计的妙处在于兼容。老设备不认识 127 打头的新指令链路管理规范早就规定了对未知指令的标准动作——回一条带错误码的拒绝。新设备跟新设备之间用新指令新跟旧之间老设备礼貌拒绝、新设备降级谁都不崩。一个字节的转义机制撑起了后来二十年的扩展空间这是分层协议留后门的教科书案例。账本末尾还有一行小字值得抄下来The following opcodes were previously used: 22, 25 to 30. All other opcodes not listed in Table 5.1 are reserved for future use.操作码 22 和 25 到 30 曾被使用如今退役表里没列出的全部保留给未来。翻旧账很有意思22 是嗅探的旧版命令后来并入了请求式的流程25 到 30 那一片属于驻留模式家族——驻留机制在 5.0 里被整体移除这些操作码随之作废。所以别看 88 条 PDU 88 个坑有几个坑是空着的墓碑记录着协议进化路上被淘汰的机制。留着编号不复用是为了防止新旧设备把同一条指令解读成两个意思。三、坐什么车DM1 与DV的捎带学问先补两个包名词。DM1 是经典蓝牙的单时隙中等速率数据包载荷最多 17 字节带纠错控制消息的默认座驾。DV 是语音数据混载包语音字段装 80 比特实时话音数据字段最多装 9 字节数据专门为通话中捎带控制信息设计。总表的 Packet type 列几乎只有两种值DM1 和 DM1/DV。翻译一下只能坐专车或者可以搭语音的顺风车。这个区分的完整规则写在基带规范里链路管理这边只给结论。原文值得整段读The ACL-C logical link shall use DM1 or DV packets. DV packets shall only be used on the ACL-C link if the ACL-C message is less than or equal to 9 bytes and an HV1 synchronous logical transport is in use. The APB-C logical link shall use DM1 packets.ACL-C 控制链路用 DM1 或 DV 包承载DV 包仅在消息不超过 9 字节且 HV1 同步逻辑传输在用时才允许广播控制链路 APB-C 只许用 DM1。HV1 又是什么它是三种 SCO 语音包型里时隙负担最重的一种每两个时隙就要占一个发包。对着总表核对这条规则会发现严丝合缝。凡是标 DM1/DV 的指令长度全部不超 9 字节特性交换正好 9 字节操作码加 8 字节特性掩码、槽偏移 9 字节、保持 7 字节、鉴权响应 5 字节凡是超过 9 字节的比如 17 字节的随机数家族配对的初始随机数、鉴权挑战、组合密钥、16 字节的信道图设置、10 字节的嗅探请求统统只标 DM1。转义指令基本全是 DM1 专车唯二例外是功率控制的请求与响应恰好 3 字节。为什么 DV 捎带非要等 HV1 在用基带规范把这件事讲得很直白SCO 时隙上的控制信息有最高优先级If the SCO slot has to be used to send control information with high priority concerning a control packet between the Central and the SCO Peripheral, or a control packet between the Central and any other Peripheral, the packet composer will discard the SCO information and use the control information instead. This control information shall be sent in a DM1 packet. Data or link control information may also be exchanged between the Central and the SCO Peripheral by using the DV or DM1 packets.如果某个 SCO 时隙必须用来发高优先级控制信息包组装器会丢弃语音信息改用控制信息且该控制信息必须用 DM1 发送中央与 SCO 外设之间的数据或链路控制信息也可以用 DV 或 DM1 交换。通话进行中每个语音时隙本来就要发包控制消息塞进 DV 的数据字段语音一比特不少、控制消息免费过江——不用为了一条功率指令单独抢一个时隙。还有个更细的补丁写在 SCO 链路流程的末尾If the SCO packet type is HV1 the LMP_ACCEPTED shall be sent using the DM1 packet.如果 SCO 包型协商的是 HV1那么对 SCO 链路请求的确认必须用 DM1 发。HV1 语音负担最重确认消息若再走 DV 捎带会和语音字段争位干脆强制专车。这些边边角角的约束全是实现者踩坑之后沉淀下来的。拿一条超长指令看它的户口直觉就更具体了。嗅探请求 10 字节、只能 DM1四、方向列的门道谁发起是设计过的Possible direction 有三种常规值C→P 只许中央发、C←P 只许外设发、C↔P 双向。粗看像废话细看全是权力分配。只许中央发的都是牵一发动全网的东西信道图设置改跳频序列必须全网一致、链路监控超时、启动与停止加密、测试激活与测试控制、服务质量。临时密钥两条也是中央专属——密钥分发的信任方向决定了话语权。只许外设发的最有意思。信道分类上报是外设的义务——外设身处干扰现场只有它知道哪些信道被 Wi-Fi 占了中央汇总后画信道图。恢复加密请求更反直觉方向是 C←P加密可以由中央暂停为了重新鉴权或换密钥但恢复的主动权交给外设。外设完成了鉴权流程、确认自己这端密钥就绪才发起恢复——把关键的确认权交给流程里最后完成的那一方避免中央单方面恢复时外设还在旧密钥状态里裸奔。五、全家福点名88 条指令按家族走一遍把 88 条 PDU 按功能分家账本立刻清爽了。一家一家过每家配一张户口页。身份能力组。名称请求与响应是一对分片协议请求带 14 比特的名称偏移响应回偏移、总长和 14 字节名称片段——设备名可以到 248 字节靠这对指令一截一截拼出来。版本交换回三样蓝牙版本号、厂商识别码、子版本号。特性交换是四条基础版一页 8 字节掩码扩展版带页号和最大支持页号特性多了就翻页。配对与鉴权组见证三代人。第一代是传统配对初始随机数撒种子组合密钥或单元密钥二选一交换鉴权挑战发随机数、应答回四字节摘要——安全强度今天看已经很弱但指令都还躺在表里。第二代是安全简单配对IO 能力交换开场封装头加封装载荷两条指令把 192 或 256 位椭圆曲线公钥拆成 16 字节一块搬运确认值与随机数两条完成承诺-披露握手DH 密钥校验收尾配上数字比较失败、通行码失败、带外失败的三个失败信号。第三代是加密管理模式与密钥长度协商启停加密暂停与恢复还有操作码 66 的 AES 暂停加密请求——密钥刷新期间临时挂起加密而不拆链路。新版本又添了密钥长度掩码一问一答两条新面孔让双方一次性摸清对方支持的密钥长度集合。功率与速率组。功率方面是四条步进指令增、减、顶格、归零加两条带响应的新版功率控制请求速率侧是首选速率宣告、自动速率允许、包型表协商外加服务质量里带轮询间隔和广播计数。时序与信道组最庞大。时钟偏移一问一答时序精度一问一答漂移加抖动嗅探和保持模式要用它算守时窗口槽偏移带地址最大槽位与请求角色切换带生效时刻监控超时AFH 三兄弟信道图设置、分类上报、分类请求时钟调整三件套加上槽可用性修改三条——为广播等时业务腾时隙的新机制定义映射、设置类型零、切换三步走。省电组四条保持与保持请求、嗅探请求、退出嗅探、嗅探子分级一问一答。语音组四条SCO 链路请求、eSCO 链路请求、各自对应的拆除请求。连接生命周期组三条连接意图宣告表示上层有业务要跑建链完成宣布链路管理层收工分离带错误码分手。测试组两条上一篇的主角。心跳一问一答用来探测链路控制层还活着没。六、Note 1 深潜eSCO 的还价艺术表尾的 Note 1 专门为 eSCO 链路请求写了六条注释先看这条指令本身——全表参数最多的 16 字节链路句柄、逻辑传输地址、时序控制标志、偏移、间隔、重传窗、双向包型、双向长度、空口编码、协商状态一样不缺注释的核心是协商状态参数的四个取值原文如下When responding to an eSCO link request with a new suggestion for these parameters, this flag may be set to 1 to indicate that the last received negotiable parameters are possible, but the new parameters specified in the response eSCO link request would be preferable, to 2 to indicate that the last received negotiable parameters are not possible as they cause a reserved slot violation or to 3 to indicate that the last received negotiable parameters would cause a latency violation. The flag is set to zero in the initiating LMP_eSCO_LINK_REQ.翻译成谈判语言发起方首次出价协商状态写 0。收到报价的一方如果全盘接受回确认完事如果对方开的条件自己勉强能干但有更优方案回一条新的 eSCO 链路请求、状态写 1意思是按你说的也行但我写的这个更好你看如果对方的条件直接踩了保留时隙红线状态写 2这是硬伤没得谈如果对方的条件会造成时延超限——对语音这种等时业务是致命的——状态写 3。一来一回之间间隔、重传窗、包型、双向长度这些可谈参数被逐步磨到双方都能接受。这是整个链路管理协议里罕见的还价式设计。绝大多数指令是一锤子买卖要么接受要么拒绝拒绝方在错误码里说明理由、发起方换个参数重来。eSCO 却把理由和反建议打包在同一条指令里把多轮重试压缩成少轮协商——语音链路建立对时延敏感省一个来回就少几十毫秒。注释里其余几条也值得记eSCO 的逻辑传输地址不许中途改绑eSCO 的包型和长度可以上下行不对称包长不再像 HV 包那样锁死。七、把账本装进代码一个最小解析器对着表写个解析器比背表快。核心逻辑就两层直码查表127 再查一层转义表顺手校验长度。# 操作码字典节选{opcode: (名称, 总长, 可否DV捎带)} LMP_PDUS { 1: (LMP_NAME_REQ, 2, True), 2: (LMP_NAME_RES, 17, False), 3: (LMP_ACCEPTED, 2, True), 4: (LMP_NOT_ACCEPTED, 3, True), 7: (LMP_DETACH, 2, True), 19: (LMP_SWITCH_REQ, 5, True), 23: (LMP_SNIFF_REQ, 10, False), 39: (LMP_FEATURES_REQ, 9, True), 43: (LMP_SCO_LINK_REQ, 7, True), 60: (LMP_SET_AFH, 16, False), 57: (LMP_TEST_CONTROL, 10, False), } ESC_PDUS { # 127 转义表{扩展码: (名称, 总长)} 0x01: (LMP_ACCEPTED_EXT, 4), 0x0C: (LMP_eSCO_LINK_REQ, 16), # 协商状态在载荷第16字节 0x1F: (LMP_POWER_CONTROL_REQ, 3), 0x15: (LMP_SNIFF_SUBRATING_REQ, 9), } def parse(pdu: bytes): if pdu[0] ! 127: name, ln, dv LMP_PDUS.get(pdu[0], (未知指令, None, False)) return name, ln, dv name, ln ESC_PDUS.get(pdu[1], (未知转义指令, None)) return name, ln, pdu[1] in (0x1F, 0x20) # 仅功率控制两条可捎带 # 例一条嗅探请求opcode 23 name, ln, dv parse(bytes([23, 0, 0x10, 0x03, 0x20, 0x00, 4, 0, 1, 0])) # LMP_SNIFF_REQ, 10字节, 不可捎带(DV) —— 超过9字节只能专车DM1解析器里那个 DV 布尔值就是第三节规则的落地超过 9 字节的指令抓包软件里看到它出现在 DV 包的数据段不用犹豫直接判实现违规。八、拿抓包分析常客、过客与三出戏规范读完后换个场景验证。第一份日志来自一段音乐播放连接的空口日志建链头两秒总表里的家族按次序集体亮相。对着log逐条看身份组特性、扩展特性、名称、版本最先登场且特性交换做了两页随后功率控制请求对端顶格发射连接管理层一声建链完成收工接着信道组发车道——信道图、信道分类使能、最大槽位、包型表、首选速率、时钟偏移、监控超时一气呵成最后安全组压轴IO 能力、公钥搬运、确认与披露、DH 校验、鉴权、开加密。值得一提的是第四行那条分离指令——这是上一次连接的分手信紧挨着新连接的建链流程新旧交替在同一屏里完成。这条时间线基本就是总表的出场顺序表先认人再修路最后上锁。第二份log来自通话链路三出戏都跟方向和状态约束有关。第一出角色切换。外设主动发起请求两阶段完成——先谈妥再执行第二阶段的指令里带着槽偏移 814 微秒这是新旧中央交接时钟基准用的。第二出协商冲突。双方几乎同时发起包型表协商各自都回确认自己那条但后到的一方被对方以事务冲突拒绝——链路管理不允许两条同类事务交叉冲突的后到者出局这正是拒绝指令家族日常最常见的工作场景。第三出状态禁入。语音链路活跃期间中央尝试发起嗅探被外设以 LMP PDU Not Allowed错误码 0x24拒绝——语音等时业务期间禁止进嗅探这是流程规则里写死的约束抓包里见到的拒绝理由跟规范条文一字不差。第三份log最有意思把整段连接的链路管理消息按类型数个数。一百七十条消息里信道图更新一家占三十一条功率控制十四条两者合计超过四成——它们是链路的日常维护队环境一变就要出动。特性、版本、名称这些身份消息集中在建链初期配对完成后就消失。这印证了总表的一个隐含分层有的指令是一生一次的引路人有的是终身值守的维护工。读协议时最该花力气的是后者因为量产设备的日常问题九成出在维护型流程上。九、收个尾88 条 PDU串起来其实是三个问题怎么编一个字节操作码加转义逃生门、怎么运DM1 专车与 DV 顺风车的九字节分界、谁有权方向列里的权力与义务分配。表本身没有一行流程描述但每条流程规则都凝结在表的列里——HV1 时确认必须走 DM1是时序冲突的解法恢复加密只许外设发起是把确认权交给流程最后完成的一方eSCO 的协商状态字段是把多轮还价压进单条指令的巧思。能把总表读出这些味道链路管理层就算真正毕业了。十、测验问题一蓝牙链路管理协议的操作码只有一个字节后来指令数量超出规划是怎么解决的老设备遇到新指令如何处理参考答案规范把 127 定义为转义操作码第一字节 127 表示转义第二字节为扩展操作码从第三字节起才是真正的指令编号等效获得第二字节操作码空间。新增的指令如嗅探子分级、安全简单配对全套、槽可用性修改、心跳等都走这条路。兼容性上不认识 127 转义指令的老设备按未知指令处理回 LMP_NOT_ACCEPTED_EXT 并带错误码新设备据此降级到旧流程双方不会失步。另外历史上操作码 22 与 25 到 30 已退役旧版嗅探指令与驻留模式家族规范保留这些编号不复用防止新旧设备对同一操作码产生不同解读。问题二LMP 控制消息什么条件下可以放在 DV 包里发送为什么超过 9 字节的指令没有这个待遇参考答案基带规范规定 ACL-C 控制链路用 DM1 或 DV 包承载但 DV 捎带有两个前提控制消息总长不超过 9 字节且 HV1 同步逻辑传输在用。DV 包是语音数据混载包语音字段 80 比特实时话音数据字段最多 9 字节控制消息塞进数据字段可以搭语音时隙的便车不为一条控制消息单独占用时隙。超过 9 字节装不下自然只能走 DM1这就是总表里 DM1/DV 与 DM1 两种标注的分界依据——特性交换、槽偏移等 9 字节指令可捎带随机数家族 17 字节、信道图 16 字节、嗅探请求 10 字节全部专车。另有补充规则HV1 包型下对 SCO 链路请求的确认必须用 DM1广播控制链路 APB-C 上的时钟调整指令也只许 DM1。问题三eSCO 链路请求里的协商状态参数有哪几个取值各自代表什么这种设计相比 SCO 有什么优势参考答案协商状态四个取值0 表示这是发起方的首次出价1 表示收到的参数可行但响应方在回程指令里给出了更优的参数建议属于还价2 表示对方参数不可行原因是造成保留时隙冲突——这是硬性约束3 表示对方参数会造成时延超限对语音等时业务不可接受。相比 SCO 链路要么接受要么拒绝、拒绝后发起方只能盲目换参数重试的模式eSCO 把拒绝理由和反建议打包在同一条指令里返回把多轮盲试压缩成少轮定向协商显著缩短语音链路建立时延。配套的灵活性还有上下行包型与长度可以不对称、包长不再锁死于 HV 包型、增加重传窗参数支持有限重传。
阅读完成 · 觉得有帮助?