news 2026/10/25 1:55:01

【LMP】规范精讲[8]: 主从易位——角色切换的时序反转与微微网重构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【LMP】规范精讲[8]: 主从易位——角色切换的时序反转与微微网重构

写加密那篇的时候挖过一个坑:重连抓包里,角色切换前出现 Stop Encryption,切换完成后 Resume Encryption 紧跟一条携带新随机数的 Start Encryption。当时只说了结论,这次把整场戏从头到尾拆开。角色切换是链路管理器里动作最复杂的流程之一:它不是发一条消息换个头衔那么简单,而是先踩刹车、再换座位、最后重新点火的一整套仪式,而且底层还藏着一次微微网身份的彻底重塑。


目录

一、为什么非要换司机

二、切换前的三道禁令

三、时隙偏移:给新司机的对时暗号

四、切换的完整剧本

4.1 切换时刻:一场约好的同时行动

4.2 底层两步走:TDD 切换与微微网切换

4.3 事后清点:什么复位、什么保留

4.4 事务标识:谁发言用哪个频道

五、主机视角:一场无感的换座

六、代码示意

七、收个尾

八、测验


先看空口log。重连的时间线:连接建立 100ms 内加密协商完成并启动;约 110ms 后中心设备停止加密,紧接着一条角色切换请求,里面除了切换时刻,还带着一个 356µs 的时隙偏移;约 312ms 后切换完成,对端发起恢复加密,中心设备随即以新随机数重新启动加密,Max Slot 也重新谈了一遍。停、切、启,三段严丝合缝。


一、为什么非要换司机

蓝牙的角色由谁定?由寻呼行为定。规范说得干脆:Since the paging device always becomes the Central of the piconet, a role switch is sometimes needed。谁发起寻呼,谁就自动成为微微网的主设备。这条铁律在单网单链的场景没问题,一到真实世界就麻烦了。

想象两个场景。第一个:手机去连耳机,手机是寻呼方,天然当主设备,皆大欢喜。第二个:一台已经当了主设备的设备,想去加入另一个现成的微微网当从设备。它必须先寻呼对方,可一寻呼它自己就成了新微微网的主设备——身份反了。怎么办?先连上,再切换。基带部分总结了两个典型场景:寻呼设备要加入现成微微网时必须降级为从设备;从设备要自立门户建新微微网、拉原主设备入伙当从设备时,必须先切换。后者就是分散网(scatternet)的根基:一台设备可以在多个微微网间分时驻留,但只能在一个网里当主设备,跨网换角色全靠这套流程。

再往深一层,角色切换不只是改个名分。微微网的一切参数——跳频序列、信道接入码、时序相位——全部派生自主设备的地址和时钟。换主设备,等于换掉整个微微网的身份证。所以规范把底层动作拆成两步:TDD 切换,收发时隙对调,司机换边坐;微微网切换,全网跳频参数换成新主设备的地址和时钟派生。这两步的细节放到后面第四节。

二、切换前的三道禁令

这么大的动作,安全把关必须前置。规范开出的条件清单很严格:

The LMP_SWITCH_REQ shall be sent only if the ACL logical transport is in Active mode, encryption is stopped or paused, and all synchronous logical transports on the same physical link are disabled.

三条禁令,逐条看。ACL 必须在 Active 模式——嗅探(Sniff)、保持(Hold)状态下不准切,因为这两种模式的时隙结构是按当前主从关系算的,切换瞬间全乱。加密必须已停止或暂停——角色切换底层要走 FHS 包等明文控制流程,密文里没法谈。基带部分同样强调:Prior to the role switch, encryption if present, shall be paused or disabled in the old piconet,还追加了禁止在嗅探保持模式和有同步链路时切换。所有同步逻辑传输(SCO/eSCO)必须已禁用,同步链路的保留时隙也是按主从关系排的,正在跑语音通话时强行切换,音频直接断流。而且协商同步链路的过程中也不准发起或接受切换,避免半途生变。

对应到log:切换请求发出前约 3ms,中心设备刚执行了 Stop Encryption;log里没有同步链路,链路处于 Active。三道禁令全部满足,切换才被允许启动。这些前置检查不是走形式,每一条背后都是一次真实的失败场景。

三、时隙偏移:给新司机的对时暗号

进入正式流程前,先解决一个精妙的时序问题。这就要请出角色切换流程里最容易被忽视的一条消息:LMP_SLOT_OFFSET。

它携带两个参数,Slot_Offset 和 BD_ADDR。定义读起来相当绕:

The Slot_Offset shall be the time, in microseconds, from the start of a Central transmission in the current piconet to the start of the next following Central transmission in the piconet where the BD_ADDR device (normally the Peripheral) is Central at the time that the request is interpreted by the BD_ADDR device.

翻译成人话:从当前微微网里主设备某次发送的起点,到目标微微网里(对方当主设备后)紧随其后的那次主设备发送起点,中间隔了多少微秒。它描述的是两张时间坐标系的相对位置。规范配的示意图一目了然:

为什么需要这个数?切换完成后,原主设备变成从设备,要在新的跳频序列上接收新主设备的包。它怎么知道该在什么精确时刻打开接收窗?你可能会说,切换时新主设备不是会发 FHS 包吗,里面带着自己的时钟。问题在于 FHS 里的时钟只有 1.25ms 的分辨率,只够对准时隙的大致格子,对不齐格子里的精确相位。基带部分把这笔账算得很清楚:时隙偏移取值范围 0 到 1249µs,分辨率 1µs,与 FHS 里的时钟信息配合使用,才能把相关窗精确摆到正确位置上。1.25ms 的粗对表加 1µs 的细对表,两级校准缺一不可。

这条消息什么时候发?规范规定了方向:谁是从设备谁发。从设备发起切换时,切换请求之前先发时隙偏移;主设备发起切换时,如果对方接受,对方(从设备)在接受的同时把自己的时隙偏移发过来。发送时机也有讲究:ACL 处于 Active、没有同步链路正在协商,随时可发。这条 PDU 标注为可选支持,但配合角色切换时它实际上是刚需——除非愿意接受降级体验:主设备发起切换时若到了切换时刻还没收到时隙偏移,切换照做,只是新从设备得靠更宽的搜索窗慢慢摸相位。

log里那条角色切换记录携带的 356µs,就是本次切换的时隙偏移,由原从设备在切换请求之前送出。

四、切换的完整剧本

前置条件满足,时隙偏移送达,正剧开场。先看 PDU 阵容和两条时序:

从设备发起:先暂停 ACL-U 链路上的用户数据流量,再发时隙偏移,最后发切换请求,等一个接受或拒绝。主设备发起:先暂停流量,发切换请求,对方若接受则回时隙偏移加接受,若拒绝直接回拒绝。两个方向的差异只在时隙偏移由谁发、何时发。

发起方的动作序列,规范写得像检查单:先暂停 ACL-U 逻辑链路上的流量;若加密模式为加密中,双方都支持暂停加密就走暂停序列,否则走停止序列(这条对应log里的 Stop Encryption);若自己是从设备,补发时隙偏移;最后发 LMP_SWITCH_REQ。应答方接受时:先确认 ACL-U 流量已暂停(没停就停)、若自己是从设备补发时隙偏移、回 LMP_ACCEPTED,然后双方执行基带切换。拒绝则回 LMP_NOT_ACCEPTED,一切照旧。

这里暂停 ACL-U 流量值得展开一句。基带部分的定义是:被链路管理器暂停后,链路控制器把当前在发的用户数据包发完并等到确认,之后就不再发任何承载 ACL-U 信息的包;恢复暂停后可继续。注意暂停的只是用户数据,控制消息走 ACL-C 链路照常收发——这就是为什么切换谈判还能继续进行。刹车踩在数据面,不踩在控制面。

4.1 切换时刻:一场约好的同时行动

LMP_SWITCH_REQ 只带一个参数 Switch_Instant,指定 TDD 切换生效的精确时刻,用主设备的蓝牙时钟表示——双方都拿得到这份钟。发起方选时刻,收下后各自起定时器,到点同时动作。

选时刻有三条规矩。原文:

This instant is chosen by the sender of the message and shall be at least 2×Tpoll or 32 (whichever is greater) slots in the future. The switch instant shall be within 12 hours of the current clock value to avoid clock wrap.

至少在 2 倍轮询间隔或 32 个时隙(取大者)之后——给应答方留足处理和传输确认的余量;不能超过 12 小时——时钟会回绕,超过这个范围时刻值就有歧义。接收方收到请求后先对表:时刻若已过期,回带错误码 Instant Passed(0x28)的拒绝;时刻在未来且回了接受,双方便各自倒计时。

定时器机制是双向保险。发起方把切换请求递给链路控制器后启动定时器,定时器到期就执行模式切换;若定时器没到就收到拒绝,停表收工。有一种边角情况规范也考虑到了:主设备发起切换、切换时刻已到但时隙偏移还没收到——切换照样执行,只是新从设备没有精确相位信息。宁可降级执行,不轻易放鸽子。

4.2 底层两步走:TDD 切换与微微网切换

到点之后,基带开始表演。假设设备 A 将成为新主设备,设备 B 是原主设备,流程分两幕:

第一幕,TDD 切换。双方在原有跳频序列上(仍用 B 的地址和时钟)同时调转收发时隙——B 开始在原接收时隙发送,A 开始在原发送时隙接收。此时时隙偏移先不用,微微网身份也还没换。切换瞬间,双方各起一个 newconnectionTO 定时器,等待对方的确认信号。

第二幕,微微网切换。新主设备 A 用旧微微网参数发出一个 FHS 包——这是蓝牙里最特殊的控制包,载荷里装着自己的地址、实时时钟、给 B 分配的新成员地址,跳频同步所需的一切都在里面。B 收到 FHS 回一个 ID 包确认,这个 ID 包仍在旧跳频序列上发,是旧世界的最后一声回响。确认之后,双方同时切换到由 A 的地址和时钟派生的新跳频序列。随后 A 发一个 POLL 包验证新信道,B 回 NULL/DM1/DH1 确认,切换落定。任何一步的定时器超时,双方都退回旧微微网、恢复旧角色,AFH 状态、数据速率、功率控制全部还原,流程可从头再来。

有个细节值得咀嚼:FHS 包里给 B 的新成员地址,和包头上 A 自己的旧地址,一新一旧同时出现在同一个包里——这个包本身就是新旧两个世界的交接文书。另外log里切换完成后 Max Slot 重新协商,对应规范的 After the role switch the ACL logical transport is reinitialized as if it were a new connection——切换后的 ACL 逻辑传输按新连接对待,序列号等状态全部重置。

4.3 事后清点:什么复位、什么保留

切换完成(无论成败)后的清点规则,规范列得非常明确。复位项:监督超时和轮询间隔回到默认值,Max_Slots 用默认值,AES-CCM 场合要用 h3 算法以新的主从地址对重算加密密钥后才能恢复加密(地址变了,密钥材料必须跟着变,这是加密篇埋的另一个坑)。保留项更有意思:

The authentication state and the ACO shall remain unchanged.

认证状态和认证密码偏移原封不动。也就是说,设备之间已经建立的信任关系不因换座位而作废——换司机不换户口,已完成的认证不必重来。还有一条兜底:

A role switch, whether successful or failed, does not affect the state of the Link Manager and of the ACL-C and ACL-U logical links except where explicitly stated。

链路管理器状态、控制链路、用户逻辑链路,除了明说的那几处,一概不受影响。切换是物理层的换血,不是协议层的革命。

对应到log:切换完成后新主设备发起恢复加密、换新随机数启动加密(AES-CCM 密钥重算流程的体现),Max Slot 重新谈到 5 槽包(链路按新连接初始化),整个过程 312ms。

4.4 事务标识:谁发言用哪个频道

最后一个小而关键的规则:切换流程里所有 PDU——时隙偏移、切换请求、对应的接受或拒绝——的事务标识,主设备发起时取 0,从设备发起时取 1。这是为并发场景设计的:双向同时发起切换时,接收方靠事务标识区分这是对方对己方请求的应答还是对方独立发起新事务,不至于把两场谈判搅在一起。

五、主机视角:一场无感的换座

链路管理器层面的风云变幻,主机基本无感。对照主机侧记录:建链命令的允许切换参数写的是是,链路策略设置里角色切换位常开;重连段加密开启事件先于切换出现,之后控制器自主完成整套切换,主机没有下发任何与切换直接相关的命令。这符合角色切换的定位——它是链路层的自治行为,主机能做的是通过建链参数和策略设置表达意愿,剩下的交给控制器。

log里还有个耐人寻味的片段:重连段切换完成后,时序记录里出现 Page Scan Repetition Mode Change,新主设备把对端的寻呼扫描重复模式改成了 R2。切换完成后新主设备接管微微网的一切管理职责,包括对端后续被寻呼时的行为调优——换完司机,连对方的作息表都顺手更新了。

六、代码示意

两个片段。第一个还原切换时刻的合法性检查,三条规矩一条不落:

#define RS_MIN_SLOTS 32 /* 保底时刻提前量 */ #define RS_MAX_AHEAD_SLOTS (12u * 3600 * 1600) /* 12 小时折成时隙 */ /* 校验发起方拟定的切换时刻, 返回 0 表示合法 */ int rs_check_instant(uint32_t clk_now, uint32_t instant, uint16_t tpoll_slots, uint32_t *err) { uint32_t min_ahead = (uint32_t)tpoll_slots * 2; if (min_ahead < RS_MIN_SLOTS) min_ahead = RS_MIN_SLOTS; if (instant - clk_now < min_ahead) { /* 至少 2xTpoll 或 32 槽 */ *err = 0x1E; /* 参数非法 */ return -1; } if (instant - clk_now > RS_MAX_AHEAD_SLOTS) { /* 防 12 小时时钟回绕 */ *err = 0x1E; return -1; } return 0; } /* 接收方对表: 时刻已过期则按规范回 0x28 */ int rs_rx_instant(uint32_t clk_now, uint32_t instant) { return (instant < clk_now) ? 0x28 : 0; /* Instant Passed */ }

第二个演示暂停 ACL-U 的语义,当前包发完等确认即停,控制消息不受影响:

/* LM 请求暂停 ACL-U: 只停用户数据, ACL-C 控制消息照常 */ void lm_pause_acl_u(struct lc *lc) { lc->acl_u_paused = true; /* LC 层行为: 当前在发的用户包继续重传直到收到 ACK, 之后静默 */ } bool lc_can_send(uint8_t llid) { if (llid == LLID_ACL_U_START || llid == LLID_ACL_U_CONT) return !lc->acl_u_paused; /* 用户数据被刹车 */ return true; /* ACL-C 控制消息永远放行 */ }

七、收个尾

角色切换把前面几篇的知识全部串了起来:它要用到版本特性交换判断对方是否支持暂停加密,要用到加密启停的完整流程,切换后还有密钥重算。整场戏可以这样记:三道禁令定前提(Active、无加密、无同步链路),一条暗号对好表(时隙偏移),一个约好的时刻(切换时刻至少 2 倍轮询间隔或 32 槽之后),底层两步换身份(TDD 切换加微微网切换,FHS 交接、POLL 验证、超时回退),事后该复位的复位、该保留的保留。下次抓包再看到停加密后面跟着那条切换请求,你看到的不是一条消息,而是一整套踩了刹车、换了座位、重新点火的完整流程。

八、测验

问题一:设备 A(主)与 B(从)已建立加密链路,A 发起角色切换前是否必须停止加密?如果双方都支持暂停加密,流程有何不同?

参考答案:

必须处理加密,但未必是停止。规范要求切换请求只能在加密已停止或暂停的状态下发送,因为底层 TDD 切换与 FHS 交换需要明文信道。区别在切换完成后:走暂停加密路径的,恢复时执行恢复加密序列;走停止加密路径的,恢复时执行完整启动加密序列并换新随机数。若使用 AES-CCM 加密,恢复前双方还必须用 h3 算法以新的主从地址对重算加密密钥——角色切换改变了地址角色,密钥材料必须随之更新。实测抓包中常见的停加密、切换、携新随机数的启动加密三段式,正是停止路径的典型表现。

问题二:切换时刻的取值有什么约束?接收方发现时刻已过如何处理?

参考答案:

三条约束。至少在 2 倍轮询间隔与 32 个时隙中的较大者之后,保证应答与确认有足够传输余量;不能超过当前时钟 12 小时,避免时钟回绕导致时刻歧义;时刻以主设备蓝牙时钟为基准表示,双方都能获取。接收方收到请求后先与主设备当前时钟比对,时刻已过则回拒绝并携带错误码 Instant Passed(0x28);时刻合法则回接受并启动定时器,到点执行切换。发起方同样有定时器,未到点先收到拒绝则停表放弃切换。

问题三:为什么角色切换底层要拆成 TDD 切换和微微网切换两步,而不是一步到位切到新跳频序列?

参考答案:

核心是时序连续性与失败回退。微微网参数派生自主设备地址和时钟,切换即换全网身份,但双方必须先有一个共同认可的时序基准完成收发对调。若直接一步切到新序列,双方在新信道上的首次互通没有可靠的同步锚点。拆成两步后:第一步在旧跳频序列上做 TDD 切换,双方仍在熟悉的信道上,只对调收发时隙,风险最低;第二步由新主设备发 FHS 包(携带地址、实时时钟、新成员地址),对端回 ID 确认,确认后才共同迁移到新序列,再由 POLL/NULL 验证新信道。任一步的 newconnectionTO 超时,双方都能明确退回旧微微网恢复旧角色,AFH 与功率控制状态同步还原。这种分步提交加超时回滚的设计,把一个不可逆的身份变更拆成了可安全重试的原子步骤。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/25 1:52:24

Codex 本地运行更简单,一键包封装环境变量和依赖

前言 很多想体验 Codex 的开发者&#xff0c;卡在前期环境部署环节。手动安装 Node.js、配置依赖&#xff0c;还要敲大量命令行&#xff0c;对于新手十分不友好&#xff0c;很容易出现各种报错。 Codex 一键安装包把环境配置、依赖安装、客户端部署全部打包封装。不需要手动配…

作者头像 李华
网站建设 2026/10/25 1:51:53

企业级 AI 底层搭建详解:网关选型、部署与治理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/25 1:49:34

不用安装 Node,Codex 免配置一键包快速部署教程

前言 很多想体验 Codex 的开发者&#xff0c;卡在前期环境部署环节。手动安装 Node.js、配置依赖&#xff0c;还要敲大量命令行&#xff0c;对于新手十分不友好&#xff0c;很容易出现各种报错。 Codex 一键安装包把环境配置、依赖安装、客户端部署全部打包封装。不需要手动配…

作者头像 李华