【android bluetooth 协议分析 06】【l2cap详解 10】【通过avdtp连接流程,感受l2cap通道的生命周期变化】
本篇我们通过分析一个具体的实例,来直观感受一下 l2cap 中通道的 状态变化。
1. 环境描述:
- 车机: a2dp sink
- 手机: a2dp source
- 场景: 手机主动 触发 连车机
声明一下: 分析的btsnoop 和 logcat 还有源码, 均是作为车机来分析的。
图中 总共分为 第一部分和第二部分。
本文的重点是分析 第一部分。 第二部分不再本篇分析之内,会将第二部分的分析单独分享在 avdtp 章节。
2. 案例分析
从第一部分看,可以分为一下6步:
- Rcvd Connection Request (AVDTP, SCID: 0x0041) : 车机侧接收到 手机发起的 avdtp 连接请求【手机 --> 车机】
- Sent Connection Response - Success (SCID: 0x0041, DCID: 0x0046): 车机向手机发送同意连接【手机 <-- 车机】
- Sent Configure Request (DCID: 0x0041): 车机发起配置请求 【手机 <-- 车机】
- Rcvd Configure Request (DCID: 0x0046):接收到手机的配置请求 【手机 --> 车机】
- Sent Configure Response - Success (SCID: 0x0041): 向手机发送配置应答 【手机 <-- 车机】
- Rcvd Configure Response - Success (SCID: 0x0046): 接收到手机的配置应答 【手机 --> 车机】
我们就依次来分析一下这6 步,在我们协议栈中是如何处理的。
上述几步都是发生在 l2cap 信令通道里面的。
- CID: L2CAP Signaling Channel (0x0001)
所以调用路径基本是在
// system/stack/l2cap/l2c_main.cc
void l2c_rcv_acl_data(BT_HDR* p_msg) {// 信令通道基本都是调用 process_l2cap_cmd 来处理/* Send the data through the channel state machine */if (rcv_cid == L2CAP_SIGNALLING_CID) {process_l2cap_cmd(p_lcb, p, l2cap_len);osi_free(p_msg);return;}}static void process_l2cap_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) {tL2C_CONN_INFO con_info;/* if l2cap command received in CID 1 on top of an LE link, ignore this* command */if (p_lcb->transport == BT_TRANSPORT_LE) {LOG_INFO("Dropping data on CID 1 for LE link");return;}/* Reject the packet if it exceeds the default Signalling Channel MTU */bool pkt_size_rej = false;if (pkt_len > L2CAP_DEFAULT_MTU) {/* Core Spec requires a single response to the first command found in a* multi-command L2cap packet. If only responses in the packet, then it* will be ignored. Here we simply mark the bad packet and decide which cmd* ID to reject later */pkt_size_rej = true;LOG_WARN("Signaling pkt_len=%d exceeds MTU size %d", pkt_len,L2CAP_DEFAULT_MTU);}uint8_t* p_next_cmd = p;uint8_t* p_pkt_end = p + pkt_len;tL2CAP_CFG_INFO cfg_info;memset(&cfg_info, 0, sizeof(cfg_info));/* An L2CAP packet may contain multiple commands */// 一个 l2cap 包中可能包含多个 命令, 所以这里 需要 while 循环遍历每一个 cmd./*想象你是一个邮局的接信柜台:1.每个 L2CAP 信令包就像一沓装在一个信封里的指令信。2.你每次拆开信封(调用 process_l2cap_cmd),要一封一封读信(while 循环)。3.每封信的信头告诉你是什么业务(连接请求、配置、断开等)。4. 你交给不同科室处理(switch-case 分发)。5. 每个科室根据信的内容,更新内部状态(状态机驱动)。*/while (true) {/* Smallest command is 4 bytes */p = p_next_cmd;if (p > (p_pkt_end - 4)) break;uint8_t cmd_code, id;uint16_t cmd_len;STREAM_TO_UINT8(cmd_code, p);STREAM_TO_UINT8(id, p);STREAM_TO_UINT16(cmd_len, p);if (cmd_len > BT_SMALL_BUFFER_SIZE) {LOG_WARN("Command size %u exceeds limit %d", cmd_len,BT_SMALL_BUFFER_SIZE);l2cu_send_peer_cmd_reject(p_lcb, L2CAP_CMD_REJ_MTU_EXCEEDED, id, 0, 0);return;}/* Check command length does not exceed packet length */p_next_cmd = p + cmd_len;if (p_next_cmd > p_pkt_end) {LOG_WARN("cmd_len > pkt_len, pkt_len=%d, cmd_len=%d, code=%d", pkt_len,cmd_len, cmd_code);break;}LOG_DEBUG("cmd_code: %d, id:%d, cmd_len:%d", cmd_code, id, cmd_len);/* Bad L2CAP packet length, look for cmd to reject */if (pkt_size_rej) {/* If command found rejected it and we're done, otherwise keep looking */if (l2c_is_cmd_rejected(cmd_code, id, p_lcb)) {LOG_WARN("Rejected command %d due to bad packet length", cmd_code);return;} else {LOG_WARN("No need to reject command %d for bad packet len", cmd_code);continue; /* Look for next cmd/response in current packet */}}// 将提前到的 cmd 分发到对应分支处理switch (cmd_code) {case L2CAP_CMD_REJECT:...break;case L2CAP_CMD_CONN_REQ:...break;case L2CAP_CMD_CONN_RSP:...break;case L2CAP_CMD_CONFIG_REQ:...break;case L2CAP_CMD_CONFIG_RSP:...break;case L2CAP_CMD_DISC_REQ:...break;case L2CAP_CMD_DISC_RSP:...break;case L2CAP_CMD_ECHO_REQ:...break;case L2CAP_CMD_INFO_REQ:...break;case L2CAP_CMD_INFO_RSP:...break;default:LOG_WARN("Bad cmd code: %d", cmd_code);l2cu_send_peer_cmd_reject(p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0,0);return;}}
}
- 我们直接从 process_l2cap_cmd 函数开始分析。
1. 车机侧接收到 avdtp 连接请求
1. log 部分
1081 2025-04-24 15:56:22.504410 vivoMobi_91:b0:62 (cbx) 22:22:96:de:b1:39 (leo 8295 chan) L2CAP 17 Rcvd Connection Request (AVDTP, SCID: 0x0041)Frame 1081: 17 bytes on wire (136 bits), 17 bytes captured (136 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 8CID: L2CAP Signaling Channel (0x0001)Command: Connection RequestCommand Code: Connection Request (0x02)Command Identifier: 0x11Command Length: 4PSM: AVDTP (0x0019)Source CID: Dynamically Allocated Channel (0x0041)[Disconnect in frame: 0]
04-24 15:56:22.504630 6130 6190 I bt_l2c_main: packages/modules/Bluetooth/system/stack/l2cap/l2c_main.cc:310 process_l2cap_cmd: cmd_code: 2, id:17, cmd_len:404-24 15:56:22.504647 6130 6190 I l2c_utils: packages/modules/Bluetooth/system/stack/l2cap/l2c_utils.cc:1356 l2cu_allocate_ccb: is_dynamic = 1, cid 0x000004-24 15:56:22.504669 6130 6190 I bt_l2cap: packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: l2cu_enqueue_ccb CID: 0x0046 priority: 204-24 15:56:22.504765 6130 6190 I l2c_link: packages/modules/Bluetooth/system/stack/l2cap/l2c_link.cc:739 l2c_link_adjust_chnl_allocation: CID:0x0046 FCR Mode:0 Priority:2 TxDataRate:1 RxDataRate:1 Quota:20004-24 15:56:22.504776 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_CLOSED [0], event=PEER_CONNECT_REQ [10]04-24 15:56:22.504783 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:199 l2c_csm_closed: LCID: 0x0046 st: CLOSED evt: PEER_CONNECT_REQ04-24 15:56:22.504798 6130 6190 I bt_btm_sec: packages/modules/Bluetooth/system/stack/btm/btm_sec.cc:1826 btm_sec_l2cap_access_req: is_originator:0, psm=0x001904-24 15:56:22.504813 6130 6190 I bt_btm_sec: packages/modules/Bluetooth/system/stack/btm/btm_sec.cc:1608 btm_sec_l2cap_access_req_by_requirement: Checking l2cap access requirements peer:xx:xx:xx:xx:b0:62 security:0x1082 is_initiator:false04-24 15:56:22.504822 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btm_find_or_alloc_dev04-24 15:56:22.504833 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_sec_l2cap_access_req_by_requirement() sm4:0x11, sec_flags:0x40be, security_required:0x1086 chk:104-24 15:56:22.504840 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: (SM4 to SM4) btm_sec_l2cap_access_req rspd. authenticated: x2, enc: x404-24 15:56:22.504846 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_sec_check_upgrade()04-24 15:56:22.504854 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_sec_is_upgrade_possible() is_possible: 0 sec_flags: 0x40be04-24 15:56:22.504862 6130 6190 I bt_btm_sec: packages/modules/Bluetooth/system/stack/btm/btm_sec.cc:4493 btm_sec_execute_procedure: security_required:0x1086 security_flags:0x40be security_state:BTM_SEC_STATE_IDLE[0]04-24 15:56:22.504869 6130 6190 I bt_btm_sec: packages/modules/Bluetooth/system/stack/btm/btm_sec.cc:4591 btm_sec_execute_procedure: Encryption not required# 安全模块检查通过
04-24 15:56:22.504876 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: Security Manager: access granted04-24 15:56:22.504883 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_sec_l2cap_access_req_by_requirement: p_dev_rec=0xb800004b18420208, clearing callback. old p_callback=0x7a3fcaf9cc# 回调 l2c_link_sec_comp
04-24 15:56:22.504893 6130 6190 I l2c_link: packages/modules/Bluetooth/system/stack/l2cap/l2c_link.cc:284 l2c_link_sec_comp2: btm_status=BTM_SUCCESS, BD_ADDR=xx:xx:xx:xx:b0:62, transport=BT_TRANSPORT_BR_EDR
2. 源码分析
#define L2CAP_CMD_CONN_REQ 0x02
// process_l2cap_cmd 中对于 Connection Request 的处理
case L2CAP_CMD_CONN_REQ: {uint16_t rcid; // 变量 rcid,用于存储对端分配的 Channel ID(Remote CID)。if (p + 4 > p_next_cmd) {// 检查当前剩余数据是否足够解析 PSM(2 字节)+ Remote CID(2 字节),总共 4 字节。如果不足说明包不完整,记录警告并直接返回。LOG_WARN("Not enough data for L2CAP_CMD_CONN_REQ");return;}// 从 PDU 中解析出 PSM 和对端为本连接指定的 CID。这里的 con_info 是一个局部或上下文结构,用于传递连接相关信息。STREAM_TO_UINT16(con_info.psm, p); // con_info.psm = 0x0019:AVDTPSTREAM_TO_UINT16(rcid, p); // rcid = 0x0041// 查找是否有注册过该 PSM 的服务(RCB = Registration Control Block),PSM 就像是协议层的端口号,只有注册了的 PSM 才能接受连接。tL2C_RCB* p_rcb = l2cu_find_rcb_by_psm(con_info.psm); // 我们在初始化就已经注册了 0x0019, 所以这里肯定是可以找到 p_rcb 的if (!p_rcb || con_info.psm == BT_PSM_ATT) {} else {// 尝试为此次连接请求分配一个新的 Channel Control Block(信道控制块),这代表一次独立的 L2CAP 信道。tL2C_CCB* p_ccb = l2cu_allocate_ccb(p_lcb, 0); // p_ccb->chnl_state = CST_CLOSED// 初始化 CCB:p_ccb->remote_id = id; // : 当前信令命令的 ID,用于后续响应时对上。p_ccb->p_rcb = p_rcb; // 记录该连接使用的是哪个 PSM 的注册服务。p_ccb->remote_cid = rcid; // 对端分配的 Channel ID。p_ccb->connection_initiator = L2CAP_INITIATOR_REMOTE; // 标记连接是对端发起的// 启动状态机,处理 `L2CAP_CONNECT_REQ` 事件,进入连接建立流程,通常会发一个 Connection Response 作为回应。l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info);break;}
总结:
- 通过 l2cu_find_rcb_by_psm 传入 psm, 找到对应的服务。 这里找到了我们之前蓝牙初始化注册进 l2cap 的 avdtp 服务。
- 通过 l2cu_allocate_ccb 分配 一个 Channel Control Block ,并且将 该通道控制块的状态设置为 CST_CLOSED。 这是该通道的初始状态。
- 通过 l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info), 触发状态机处理 L2CEVT_L2CAP_CONNECT_REQ 事件。
1. l2cu_find_rcb_by_psm
- 函数声明:定义一个名叫
l2cu_find_rcb_by_psm
的函数。 - 输入参数:
psm
,是一个uint16_t
类型的数,表示要查找的 PSM (Protocol/Service Multiplexer) - 返回值:返回一个指向
tL2C_RCB
结构体的指针。如果找到匹配的RCB
(Registration Control Block),就返回它;如果找不到,就返回NULL
。
tL2C_RCB* l2cu_find_rcb_by_psm(uint16_t psm) {/*l2cb: 是 L2CAP 层的一个全局控制块(通常是 tL2C_CB 结构体),里面有一个 rcb_pool[]数组.rcb_pool[]:保存了所有注册到 L2CAP 层的 PSM 服务,属于服务注册表。*/tL2C_RCB* p_rcb = &l2cb.rcb_pool[0]; // 准备从第一个注册表条目开始遍历。uint16_t xx;// 遍历到最大条目数 MAX_L2CAP_CLIENTS(即最大支持的 L2CAP 客户端数), 目前是 15for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) {/*p_rcb->in_use:这个 RCB 是否正在使用?(即注册了)p_rcb->psm == psm:这个 RCB 的 PSM 是否和我们要找的 psm相同?*/if ((p_rcb->in_use) && (p_rcb->psm == psm)) return (p_rcb);}/* If here, no match found */return (NULL);
}
代码的作用:
项目 | 内容 |
---|---|
功能 | 在 l2cb.rcb_pool[] 中查找一个 符合给定 PSM 的服务注册块(RCB) |
输入 | 一个 16 位的 psm |
输出 | 匹配到的 tL2C_RCB 指针,或者 NULL |
遍历方式 | 顺序遍历,从索引 0 开始直到 MAX_L2CAP_CLIENTS |
匹配条件 | in_use == true 并且 psm == 输入 psm |
应用场景:
- 当 L2CAP 收到某个连接请求时(例如接收到
L2CAP_CONNECT_REQ
),需要根据psm
查找对应的服务。 - 如果找不到,就要拒绝连接请求。
比如:
- 手机请求通过
0x0019
(AVDTP)连接车机。 - 车机收到后,会调用
l2cu_find_rcb_by_psm(0x0019)
查找是否有注册的 AVCTP 服务处理它。
2. l2cu_allocate_ccb
-
函数名字:
l2cu_allocate_ccb
-
作用:从 CCB池(Channel Control Block池)里分配一个新的 CCB,建立一个逻辑信道。
-
参数:
p_lcb
:指向对应物理链路(LCB)的指针,代表哪个设备的连接。cid
:如果是 0,表示系统自己分配;如果非 0,表示希望指定cid
分配。
// system/stack/l2cap/l2c_utils.cc
tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) {LOG_INFO("is_dynamic = %d, cid 0x%04x", p_lcb != nullptr, cid); // 输出 p_lcb 是否为空(动态通道?)以及要分配的 CID。/*l2cb.p_free_ccb_first:指向第一个空闲的 CCB。如果为空,说明没有可用 CCB了,直接报错并返回 nullptr。*/if (!l2cb.p_free_ccb_first) {LOG_ERROR("First free ccb is null for cid 0x%04x", cid);return nullptr;}tL2C_CCB* p_ccb; // 定义一个指针 p_ccb,用于保存即将分配的 CCB。// 判断 cid 是不是系统自动分配/* If a CID was passed in, use that, else take the first free one */if (cid == 0) {// 如果 cid==0,需要从自由链表(free list)拿第一个空闲的 CCB。p_ccb = l2cb.p_free_ccb_first; // 拿走第一个空闲的 CCB。l2cb.p_free_ccb_first = p_ccb->p_next_ccb; // 把空闲链表的头指针移动到下一个空闲节点。} else { // 用户指定 cid,// 如果 cid != 0,需要找出对应 cid 的 CCBtL2C_CCB* p_prev = nullptr;/*1. L2CAP 的动态信道 CID 是从 L2CAP_BASE_APPL_CID 开始的(0x0040).2. 数组是连续的,所以可以直接通过 (cid - BASE) 定位到对应的 CCB.*/p_ccb = &l2cb.ccb_pool[cid - L2CAP_BASE_APPL_CID];// 从空闲链表里移除if (p_ccb == l2cb.p_free_ccb_first) {// 如果这块 CCB恰好是链表头,直接更新空闲链表头指针。l2cb.p_free_ccb_first = p_ccb->p_next_ccb;} else {// 如果不是链表头,需要在链表中查找这个 CCB 的前一个节点for (p_prev = l2cb.p_free_ccb_first; p_prev != nullptr;p_prev = p_prev->p_next_ccb) {if (p_prev->p_next_ccb == p_ccb) {// 找到以后,把前一个节点的 next 指针指向 p_ccb->p_next_ccb,实现链表断开。p_prev->p_next_ccb = p_ccb->p_next_ccb;if (p_ccb == l2cb.p_free_ccb_last) {// 如果删的是链表最后一个,更新 p_free_ccb_lastl2cb.p_free_ccb_last = p_prev;}break;}}if (p_prev == nullptr) {// 如果循环结束没找到,说明链表里没有这个 CCB,报错并返回空。LOG_ERROR("Could not find CCB for CID 0x%04x in the free list", cid);return nullptr;}}}// 初始化 CCB 内容p_ccb->p_next_ccb = p_ccb->p_prev_ccb = nullptr; // 断开当前 CCB 的链表关系(确保干净)p_ccb->in_use = true; // 标记:正在使用/* Get a CID for the connection */p_ccb->local_cid = L2CAP_BASE_APPL_CID + (uint16_t)(p_ccb - l2cb.ccb_pool); // 根据数组偏移重新计算本地 CID. 在当前场景下计算出来是 0x0046// 初始化 CCB 其他关联字段p_ccb->p_lcb = p_lcb; // 绑定 LCBp_ccb->p_rcb = nullptr; // 还没绑定 RCB(服务)/* Set priority then insert ccb into LCB queue (if we have an LCB) */p_ccb->ccb_priority = L2CAP_CHNL_PRIORITY_LOW; // 默认优先级是低优先级。// 将 CCB 插入到 LCB 的 CCB链表if (p_lcb) l2cu_enqueue_ccb(p_ccb); // 如果有对应物理连接,把 CCB 挂到物理连接(LCB)的 CCB链表里// 配置默认配置项/* Put in default values for configuration */memset(&p_ccb->our_cfg, 0, sizeof(tL2CAP_CFG_INFO)); // 清空本地配置和对端配置结构体memset(&p_ccb->peer_cfg, 0, sizeof(tL2CAP_CFG_INFO));// 配置默认参数(两边一样), 初始化 MTU、Flush超时时间、QOS参数等,都是默认值。/* Put in default values for local/peer configurations */p_ccb->our_cfg.flush_to = p_ccb->peer_cfg.flush_to = L2CAP_NO_AUTOMATIC_FLUSH;p_ccb->our_cfg.mtu = p_ccb->peer_cfg.mtu = L2CAP_DEFAULT_MTU;p_ccb->our_cfg.qos.service_type = p_ccb->peer_cfg.qos.service_type =L2CAP_DEFAULT_SERV_TYPE;p_ccb->our_cfg.qos.token_rate = p_ccb->peer_cfg.qos.token_rate =L2CAP_DEFAULT_TOKEN_RATE;p_ccb->our_cfg.qos.token_bucket_size = p_ccb->peer_cfg.qos.token_bucket_size =L2CAP_DEFAULT_BUCKET_SIZE;p_ccb->our_cfg.qos.peak_bandwidth = p_ccb->peer_cfg.qos.peak_bandwidth =L2CAP_DEFAULT_PEAK_BANDWIDTH;p_ccb->our_cfg.qos.latency = p_ccb->peer_cfg.qos.latency =L2CAP_DEFAULT_LATENCY;p_ccb->our_cfg.qos.delay_variation = p_ccb->peer_cfg.qos.delay_variation =L2CAP_DEFAULT_DELAY;// FCR(流控制/重传)部分初始化p_ccb->peer_cfg_already_rejected = false; // 流控配置默认没拒绝,还能尝试多次。p_ccb->fcr_cfg_tries = L2CAP_MAX_FCR_CFG_TRIES;// 创建ACK、监控重传定时器alarm_free(p_ccb->fcrb.ack_timer); // 之前如果存在老的,先释放掉。p_ccb->fcrb.ack_timer = alarm_new("l2c_fcrb.ack_timer"); // 创建ACK超时定时器/* CSP408639 Fix: When L2CAP send amp move channel request or receive* L2CEVT_AMP_MOVE_REQ do following sequence. Send channel move* request -> Stop retrans/monitor timer -> Change channel state to* CST_AMP_MOVING. */alarm_free(p_ccb->fcrb.mon_retrans_timer);p_ccb->fcrb.mon_retrans_timer = alarm_new("l2c_fcrb.mon_retrans_timer"); // 创建监控重传定时器// 接收MTU、发送MPS设置, 最大接收SDU大小、最大传输单元大小。p_ccb->max_rx_mtu = BT_DEFAULT_BUFFER_SIZE -(L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET + L2CAP_FCS_LEN);p_ccb->tx_mps = BT_DEFAULT_BUFFER_SIZE - 32;// 创建四个 发送/接收队列, 四个队列分别保存发送缓存、重传缓存、选择重传缓存等。p_ccb->xmit_hold_q = fixed_queue_new(SIZE_MAX);p_ccb->fcrb.srej_rcv_hold_q = fixed_queue_new(SIZE_MAX);p_ccb->fcrb.retrans_q = fixed_queue_new(SIZE_MAX);p_ccb->fcrb.waiting_for_ack_q = fixed_queue_new(SIZE_MAX);// 初始化其他小字段, 是否发生拥塞的标志、缓冲区配额。p_ccb->cong_sent = false;p_ccb->buff_quota = 2; /* This gets set after config *//* If CCB was reserved Config_Done can already have some value */if (cid == 0) { // 如果是系统分配 CID, 配置是否完成标志位。p_ccb->config_done = 0;} else {LOG_INFO("cid 0x%04x config_done:0x%x", cid, p_ccb->config_done);}// 初始化频道状态机状态p_ccb->chnl_state = CST_CLOSED; // 初始是 关闭状态p_ccb->flags = 0; // 各种 flag 置 0// 数据传输速率、是否可 flush// 默认都是低速率传输,不支持 flush,不支持 ECOC (Enhanced Credit-Based flow control)。p_ccb->tx_data_rate = L2CAP_CHNL_DATA_RATE_LOW;p_ccb->rx_data_rate = L2CAP_CHNL_DATA_RATE_LOW;p_ccb->is_flushable = false;p_ccb->ecoc = false;// 创建 CCB 通用超时定时器alarm_free(p_ccb->l2c_ccb_timer);p_ccb->l2c_ccb_timer = alarm_new("l2c.l2c_ccb_timer"); // 超时时钟,用于各种 L2CAP 超时操作// 标记是否待移除, 初始不待删除p_ccb->pending_remove = false;// 调整 LCB 上信道的内存资源分配。l2c_link_adjust_chnl_allocation();// 更新 LCB 状态if (p_lcb != NULL) {// 如果 LCB 存在,说明本地客户端已经活跃了// once a dynamic channel is opened, timeouts become activep_lcb->with_active_local_clients = true;}// 最后返回新分配的 CCBreturn p_ccb;
}
项目 | 内容 |
---|---|
主要功能 | 分配/初始化一个新的 L2CAP 信道控制块(CCB) |
输入参数 | p_lcb (链路指针), cid (信道ID) |
是否支持动态/指定CID | 支持 |
核心步骤 | 从空闲链表拿一个 CCB,初始化默认参数,绑定LCB,配置超时定时器 |
失败情况 | 空闲链表为空;或者指定cid找不到 |
返回 | 新分配的 tL2C_CCB* |
3. L2CEVT_L2CAP_CONNECT_REQ 事件处理
L2CEVT_L2CAP_CONNECT_REQ = 10, /* request */l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info);
// system/stack/l2cap/l2c_csm.ccvoid l2c_csm_execute(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {LOG_INFO("Entry chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);switch (p_ccb->chnl_state) {case CST_CLOSED:l2c_csm_closed(p_ccb, event, p_data);break;...}
}
l2c_csm_closed
是 L2CAP 信道状态机中处理 “CLOSED” 状态的函数。p_ccb
:当前信道的控制块(Channel Control Block)。event
:触发的事件(如:连接请求、断开等)。p_data
:附带数据,不同事件传递的内容不一样(这里只针对 CONNECT_REQ)。
static void l2c_csm_closed(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {// 把 p_data 强转成连接信息结构体 tL2C_CONN_INFO*,虽然本段后面没实际用到 p_ci。tL2C_CONN_INFO* p_ci = (tL2C_CONN_INFO*)p_data;uint16_t local_cid = p_ccb->local_cid; // local_cid 保存本地 CID,后续打印方便tL2CA_DISCONNECT_IND_CB* disconnect_ind; // disconnect_ind 准备保存"断开连接回调函数"指针// 检查是否注册了对应的 RCBif (p_ccb->p_rcb == NULL) {/*p_rcb(Registration Control Block)如果为空,表示这个信道没有注册应用者,那就直接打印错误日志并返回。因为如果没人注册,就没必要继续处理了.*/LOG_ERROR("LCID: 0x%04x st: CLOSED evt: %s p_rcb == NULL",p_ccb->local_cid, l2c_csm_get_event_name(event));return;}/*取断开回调函数从 RCB 结构中取出断开连接的回调函数(虽然本段代码中暂时没用到 disconnect_ind,留作未来其他分支扩展)。*/disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb;// 打印当前 LCID、状态(CLOSED)、事件名称。LOG_INFO("LCID: 0x%04x st: CLOSED evt: %s", p_ccb->local_cid,l2c_csm_get_event_name(event));switch (event) {// 处理我们刚设置的 L2CEVT_L2CAP_CONNECT_REQ 事件case L2CEVT_L2CAP_CONNECT_REQ:// 收到来自对端的连接请求时,如果当前状态是 CLOSED,要走建立连接流程。/* stop link timer to avoid race condition between A2MP, Security, and* L2CAP * * 取消 link 层定时器(避免 race condition):* 停止 link 级别的超时定时器,避免和 A2MP(Alternate MAC/PHY)、Security、L2CAP 同时抢占流程导致竞态问题。* */alarm_cancel(p_ccb->p_lcb->l2c_lcb_timer);// 判断是不是 BLE 连接if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) {// 如果是 BLE 连接(而不是传统 BR/EDR 蓝牙)。// BLE 下安全验证p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; // 先切换状态机到 "等待安全完成"(W4_SEC_COMP)。// 发起 BLE 认证请求(比如加密/配对),要求远端满足安全条件。// 回调函数是 l2c_link_sec_comp2,附带 p_ccbtL2CAP_LE_RESULT_CODE result = l2ble_sec_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, false,&l2c_link_sec_comp2, p_ccb);// 根据安全检查结果处理switch (result) {case L2CAP_LE_RESULT_INSUFFICIENT_AUTHORIZATION:case L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS:case L2CAP_LE_RESULT_INVALID_PARAMETERS:case L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION:case L2CAP_LE_RESULT_INSUFFICIENT_ENCRYP_KEY_SIZE:case L2CAP_LE_RESULT_INSUFFICIENT_ENCRYP:// 如果认证失败(各种错误原因),需要:l2cu_reject_ble_connection(p_ccb, p_ccb->remote_id, result); // 发送拒绝连接l2cu_release_ccb(p_ccb); // 释放掉分配的 p_ccb 控制块break;case L2CAP_LE_RESULT_CONN_OK:case L2CAP_LE_RESULT_NO_PSM:case L2CAP_LE_RESULT_NO_RESOURCES:case L2CAP_LE_RESULT_INVALID_SOURCE_CID:case L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED:// 如果是连接成功或者一些允许继续的情况,不做处理,后续等待回调break;}} else {// 传统 BR/EDR 连接下的处理// 尝试设置 Link Policy(Active Mode)// 把连接设置为 Active Mode(主动通讯模式),防止进入低功耗模式导致通信卡住。if (!BTM_SetLinkPolicyActiveMode(p_ccb->p_lcb->remote_bd_addr)) {// 如果设置失败,打印警告。LOG_WARN("Unable to set link policy active");}p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; // 先切换状态机到 "等待安全完成"(W4_SEC_COMP)。// 发起传统蓝牙安全认证// 通过 BTM(蓝牙管理模块)发起安全检查(加密认证等)。// 成功返回 BTM_CMD_STARTED,代表认证正在进行。auto status = btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr,p_ccb->p_rcb->psm, false,&l2c_link_sec_comp, p_ccb);if (status == BTM_CMD_STARTED) {// 如果认证正在进行,回复"连接等待中"// started the security process, tell the peer to set a longer timer// 回复 Peer 一个连接挂起(Pending)消息,告诉对方“我在验证中,请稍等”。l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0);} else {// 如果不是正在进行,打印认证状态日志。LOG_INFO("Check security for psm 0x%04x, status %d",p_ccb->p_rcb->psm, status);}}break;default:LOG_ERROR("Handling unexpected event:%s", l2c_csm_get_event_name(event));}// 最后统一打印一下当前信道状态和处理的事件,便于跟踪调试。LOG_INFO("Exit chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);
}
步骤 | 作用 |
---|---|
检查 RCB 是否存在 | 确保有注册应用 |
处理 CONNECT_REQ | 根据是 BLE 还是 BR/EDR 分别走安全验证流程 |
设置 Link Active Mode | 保持连接活跃 |
BLE 下可能直接拒绝连接 | 如果认证失败 |
传统蓝牙下可能发 Pending | 如果认证在进行 |
其他事件打印异常日志 | CLOSED 状态不应该接其他事件 |
4. 传统蓝牙安全认证
-
定义了一个函数
btm_sec_l2cap_access_req
。 -
功能:处理 L2CAP 在尝试建立连接时,询问安全模块(BTM,即 Bluetooth Manager)是否允许这条连接。
-
输入参数:
-
bd_addr
:对端设备的蓝牙地址。 -
psm
:L2CAP 层的协议/服务多路复用器编号(Protocol/Service Multiplexer,比如 AVDTP 是 0x0019)。 -
is_originator
:表示发起方是本地设备(true)还是对端设备(false)。 -
p_callback
:当需要异步处理(比如需要等待安全认证完成)时调用的回调函数。 -
p_ref_data
:透传数据,回调时一起带回。
-
// system/stack/btm/btm_sec.cc/********************************************************************************* Function btm_sec_l2cap_access_req** Description This function is called by the L2CAP to grant permission to* establish L2CAP connection to or from the peer device.** Parameters: bd_addr - Address of the peer device* psm - L2CAP PSM* is_originator - true if protocol above L2CAP originates* connection* p_callback - Pointer to callback function called if* this function returns PENDING after required* procedures are complete. MUST NOT BE NULL.** Returns tBTM_STATUS*******************************************************************************/
tBTM_STATUS btm_sec_l2cap_access_req(const RawAddress& bd_addr, uint16_t psm,bool is_originator,tBTM_SEC_CALLBACK* p_callback,void* p_ref_data) {/*定义固定变量 transport,设置为 BR/EDR 传输类型(经典蓝牙)。注:并没有处理 LE(低功耗蓝牙) 的情况,因为这是给 BR/EDR 传统连接用的。*/// should check PSM range in LE connection oriented L2CAP connectionconstexpr tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR;// 打日志,记录当前是不是发起方 (is_originator),以及用的是什么 psmLOG_INFO("is_originator:%d, psm=0x%04x", is_originator, psm);/*找出注册在安全模块中、匹配这个 psm 的服务记录(tBTM_SEC_SERV_REC 类型)。btm_sec_find_first_serv 会查一张内部表,看看这个 psm 是不是有应用程序注册过。如果找不到,说明这个 psm 未注册,不应该允许连接。*/// Find the service record for the PSMtBTM_SEC_SERV_REC* p_serv_rec = btm_sec_find_first_serv(is_originator, psm);// If there is no application registered with this PSM do not allow connectionif (!p_serv_rec) {/*如果没有找到服务记录:打一条警告日志,说没有应用注册这个 psm。调用传进来的回调函数 p_callback,告知请求失败(BTM_MODE_UNSUPPORTED)。返回 BTM_MODE_UNSUPPORTED,表示不支持连接。*/LOG_WARN("PSM: 0x%04x no application registered", psm);(*p_callback)(&bd_addr, transport, p_ref_data, BTM_MODE_UNSUPPORTED);return (BTM_MODE_UNSUPPORTED);}/* Services level0 by default have no security */if (psm == BT_PSM_SDP) {/*特殊处理:如果是 SDP(Service Discovery Protocol),不需要任何安全认证。直接调用回调函数,返回成功(BTM_SUCCESS_NO_SECURITY),并退出.解释:SDP 只是用来查服务列表,不涉及数据传输或隐私,所以不要求加密认证。*/LOG_INFO("No security required for SDP");(*p_callback)(&bd_addr, transport, p_ref_data, BTM_SUCCESS_NO_SECURITY);return (BTM_SUCCESS);}uint16_t security_required;if (btm_cb.security_mode == BTM_SEC_MODE_SC) {/*接下来,根据当前蓝牙堆栈的安全模式(btm_cb.security_mode),确定需要什么级别的安全措施。如果是 安全模式 4(Secure Connections Only,强制 LE Secure Connection 加密),就调整一下服务的 security_flags。*/security_required = btm_sec_set_serv_level4_flags(p_serv_rec->security_flags, is_originator);} else {// 否则,直接使用服务本身注册时要求的安全标志。security_required = p_serv_rec->security_flags;}/*最后,把地址、最终确定的 security_required、是否是发起方、回调函数、透传数据,都传给更细粒度的处理函数 btm_sec_l2cap_access_req_by_requirement。*//*这个函数负责具体执行:检查配对情况,是否加密,是否认证,是否触发配对流程等。*/return btm_sec_l2cap_access_req_by_requirement(bd_addr, security_required, is_originator, p_callback, p_ref_data);
}
- 当 L2CAP 想建立连接时,调用这个函数,请求安全模块 BTM 检查是否允许建立连接。安全模块根据 PSM 类型和安全要求,决定直接通过、触发安全认证,还是直接拒绝。
安全等级介绍
// system/stack/include/btm_api_types.h
/* BTM_SEC security masks */
enum : uint16_t {/* Nothing required */BTM_SEC_NONE = 0x0000,/* Inbound call requires authentication */BTM_SEC_IN_AUTHENTICATE = 0x0002,/* Inbound call requires encryption */BTM_SEC_IN_ENCRYPT = 0x0004,/* Outbound call requires authentication */BTM_SEC_OUT_AUTHENTICATE = 0x0010,/* Outbound call requires encryption */BTM_SEC_OUT_ENCRYPT = 0x0020,/* Secure Connections Only Mode */BTM_SEC_MODE4_LEVEL4 = 0x0040,/* Need to switch connection to be central */BTM_SEC_FORCE_CENTRAL = 0x0100,/* Need to switch connection to be central */BTM_SEC_ATTEMPT_CENTRAL = 0x0200,/* Need to switch connection to be peripheral */BTM_SEC_FORCE_PERIPHERAL = 0x0400,/* Try to switch connection to be peripheral */BTM_SEC_ATTEMPT_PERIPHERAL = 0x0800,/* inbound Do man in the middle protection */BTM_SEC_IN_MITM = 0x1000,/* outbound Do man in the middle protection */BTM_SEC_OUT_MITM = 0x2000,/* enforce a minimum of 16 digit for sec mode 2 */BTM_SEC_IN_MIN_16_DIGIT_PIN = 0x4000,
};
宏定义 | 含义 | 适用方向 | 常见使用场景 |
---|---|---|---|
BTM_SEC_NONE (0x0000) | 不要求任何安全性 | 入站+出站 | 非敏感数据传输,比如一些开放设备广播、简单配对连接 |
BTM_SEC_IN_AUTHENTICATE (0x0002) | 入站连接需要身份验证(配对) | 入站(被连接方) | 设备作为服务器,要求连接进来的客户端至少完成配对,如蓝牙耳机接收连接 |
BTM_SEC_IN_ENCRYPT (0x0004) | 入站连接需要加密 | 入站 | 设备被连接时,要求链路加密,比如医疗设备传输隐私数据 |
BTM_SEC_OUT_AUTHENTICATE (0x0010) | 出站连接需要身份验证(配对) | 出站(发起连接方) | 手机主动连接智能手表,要求连接前认证对方身份 |
BTM_SEC_OUT_ENCRYPT (0x0020) | 出站连接需要加密 | 出站 | 手机主动连接蓝牙锁,要求链路加密保障通信安全 |
BTM_SEC_MODE4_LEVEL4 (0x0040) | 只允许 Secure Connections Only 模式连接(使用更强的 ECDH 算法) | 入站+出站 | 高安全需求场景,如车载蓝牙连接手机时要求强加密,拒绝传统配对 |
BTM_SEC_FORCE_CENTRAL (0x0100) | 强制切换角色为 Central(主机) | 出站 | 手机强制作为 Central 主动连接外围设备,比如蓝牙耳机 |
BTM_SEC_ATTEMPT_CENTRAL (0x0200) | 尝试切换为 Central(若失败也继续) | 出站 | 优先作为 Central 连接,但失败不会终止连接,比如车机同时可以是 Peripheral 和 Central |
BTM_SEC_FORCE_PERIPHERAL (0x0400) | 强制切换角色为 Peripheral(从机) | 入站 | 设备必须作为 Peripheral 接受连接,例如体重秤作为从设备 |
BTM_SEC_ATTEMPT_PERIPHERAL (0x0800) | 尝试切换为 Peripheral | 入站 | 优先作为 Peripheral,如果不行再考虑其它,比如耳机可以接收连接也可以发起 |
BTM_SEC_IN_MITM (0x1000) | 入站连接要求防中间人攻击(需要 MITM 保护的认证) | 入站 | 接收连接时要求用户确认(比如 PIN 码、数字比较),提高安全性,如支付终端 |
BTM_SEC_OUT_MITM (0x2000) | 出站连接要求防中间人攻击 | 出站 | 主动发起连接时要求进行 MITM 保护认证,如安全支付用手机连接 POS 机 |
BTM_SEC_IN_MIN_16_DIGIT_PIN (0x4000) | 入站要求使用至少16位 PIN 码(传统配对方式) | 入站 | 医疗设备或企业环境中要求高强度 PIN 码配对,防止弱 PIN 导致的安全风险 |
总结一下:
IN_*
的宏是指被连接时要求的安全措施;OUT_*
的宏是指主动发起连接时要求的安全措施;MODE4_LEVEL4
是专门为了**强制使用 LE Secure Connections (SC Only)**而设的,只有支持 ECDH 的设备才能连;FORCE
和ATTEMPT
是关于**角色切换(Central/Peripheral)**的,不是直接和安全有关;MITM
和MIN_16_DIGIT_PIN
都是加强认证强度,防止暴力破解或中间人攻击。
#define BTM_SEC_OUT_LEVEL4_FLAGS \(BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_ENCRYPT | BTM_SEC_OUT_MITM | \BTM_SEC_MODE4_LEVEL4)#define BTM_SEC_IN_LEVEL4_FLAGS \(BTM_SEC_IN_AUTHENTICATE | BTM_SEC_IN_ENCRYPT | BTM_SEC_IN_MITM | \BTM_SEC_MODE4_LEVEL4)// system/stack/btm/btm_sec.cc
static uint16_t btm_sec_set_serv_level4_flags(uint16_t cur_security,bool is_originator) {uint16_t sec_level4_flags =is_originator ? BTM_SEC_OUT_LEVEL4_FLAGS : BTM_SEC_IN_LEVEL4_FLAGS;return cur_security | sec_level4_flags;
}
项目 | 含义 | 为什么需要 | 备注 |
---|---|---|---|
BTM_SEC_OUT_AUTHENTICATE (或 BTM_SEC_IN_AUTHENTICATE ) | 需要进行认证(完成配对过程,建立可信连接) | Level 4 要求必须经过身份认证,不允许匿名连接 | 需要交换身份信息(如密钥、证书) |
BTM_SEC_OUT_ENCRYPT (或 BTM_SEC_IN_ENCRYPT ) | 需要链路加密(建立加密连接) | Level 4 要求链路必须加密,防止数据被窃听 | 通常在认证成功后自动加密 |
BTM_SEC_OUT_MITM (或 BTM_SEC_IN_MITM ) | 需要防中间人攻击的认证(MITM protection) | Level 4 要求配对过程中必须防止中间人攻击 | 常见方式:用户确认、数字比较、密钥输入 |
BTM_SEC_MODE4_LEVEL4 | 要求使用 Secure Connections Only 模式(基于 ECDH 的强安全配对) | 这是 Level 4 的核心要求:只允许 LE Secure Connections (LESC),不接受传统配对(Legacy Pairing) | 需要设备双方都支持 Bluetooth 4.2+ 的 LESC 特性 |
BTM_SEC_OUT_LEVEL4_FLAGS
👉 表示当主动发起连接时,要强制要求:
- 配对认证
- 加密链路
- 有中间人保护
- 使用 LE Secure Connections Only 模式
BTM_SEC_IN_LEVEL4_FLAGS
👉 表示当被连接时,同样要强制要求:
- 配对认证
- 加密链路
- 有中间人保护
- 使用 LE Secure Connections Only 模式
这两个宏分别用于不同方向的连接,但要求完全一样,都是 Level 4。
什么是 Bluetooth Secure Connections Only Mode Level 4?
等级 | 描述 | 特点 |
---|---|---|
Level 1 | 不要求认证也不要求加密 | 开放连接,最弱 |
Level 2 | 不要求认证但要求加密 | 加密但不认证 |
Level 3 | 需要认证和加密,但可以是传统配对(Legacy Pairing) | 有一定防护 |
Level 4 | 需要认证、加密,并且必须使用 LE Secure Connections(ECDH密钥交换) | 最高级别安全,防中间人攻击,适合敏感数据传输,比如支付、医疗、车载蓝牙连接 |
Level 4 是从 Bluetooth 4.2 标准开始要求的,需要设备硬件和软件都支持 Secure Connections (SC) 特性。
例子:
- 车机连接手机(比如 BMW 蓝牙连接 iPhone)
👉 必须使用 Secure Connections Only,避免有人中途劫持蓝牙连接控制车机 - 手机连接支付 POS 机
👉 必须要求认证、防中间人攻击、并且加密,保护交易安全 - 智能门锁连接手机
👉 不仅要认证,还要防止别人假冒设备或劫持指令
BTM_SEC_OUT_LEVEL4_FLAGS
和 BTM_SEC_IN_LEVEL4_FLAGS
是出站/入站连接时强制应用 Secure Connections Only Level 4 安全要求的一组标志,确保连接经过认证、加密、防中间人攻击,并使用最新的安全机制(LESC)。
btm_sec_l2cap_access_req_by_requirement
这个函数是 L2CAP 建立连接时,蓝牙安全管理模块(BTM)用来检查是否满足安全要求 的关键步骤。
如果安全性(比如加密、认证)不够,就会触发配对/加密流程。
入参:
bd_addr
:对方蓝牙设备地址。security_required
:希望达到的安全要求(比如必须认证、加密、Secure Connections等)。is_originator
:自己是发起方(true)还是接受方(false)。p_callback
:安全处理完成后的回调函数。p_ref_data
:回调时传递的用户自定义数据。
// system/stack/btm/btm_sec.cctBTM_STATUS btm_sec_l2cap_access_req_by_requirement(const RawAddress& bd_addr, uint16_t security_required, bool is_originator,tBTM_SEC_CALLBACK* p_callback, void* p_ref_data) {// 记录当前请求的设备地址、请求的安全等级,以及我是发起方还是接受方。LOG_INFO("Checking l2cap access requirements peer:%s security:0x%x ""is_initiator:%s",PRIVATE_ADDRESS(bd_addr), security_required,logbool(is_originator).c_str());// 1. 初始化变量tBTM_STATUS rc = BTM_SUCCESS; // 初始设为成功bool chk_acp_auth_done = false; // 标记是否需要检查认证状态/* should check PSM range in LE connection oriented L2CAP connection */constexpr tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; // 这里明确只针对 传统蓝牙(BR/EDR) 处理/* Find or get oldest record */// 2. 查找设备记录tBTM_SEC_DEV_REC* p_dev_rec = btm_find_or_alloc_dev(bd_addr); // 找到或创建一条安全记录(每个连接的设备在 BTM 都有一份安全记录)。p_dev_rec->hci_handle = BTM_GetHCIConnHandle(bd_addr, BT_TRANSPORT_BR_EDR); // 绑定 HCI 连接句柄。// 3. 被动连接且要求 Secure Connections Level 4 的校验if ((!is_originator) && (security_required & BTM_SEC_MODE4_LEVEL4)) {/*如果我是接受方,且要求Mode 4 Level 4(即 Secure Connections 的最高等级),那么:1. 检查本地和对方是否支持 Secure Connections。2. 如果任何一方不支持,拒绝连接。3. 比如要求超高安全性的支付终端,只接受支持 Secure Connections 的设备*/bool local_supports_sc =controller_get_interface()->supports_secure_connections();/* acceptor receives L2CAP Channel Connect Request for Secure Connections* Only service */if (!local_supports_sc || !p_dev_rec->SupportsSecureConnections()) {LOG_WARN("Policy requires mode 4 level 4, but local_support_for_sc=%d, ""rmt_support_for_sc=%s, failing connection",local_supports_sc,logbool(p_dev_rec->SupportsSecureConnections()).c_str());if (p_callback) {(*p_callback)(&bd_addr, transport, (void*)p_ref_data,BTM_MODE4_LEVEL4_NOT_SUPPORTED);}return (BTM_MODE4_LEVEL4_NOT_SUPPORTED);}}/* there are some devices (moto KRZR) which connects to several services at* the same time *//* we will process one after another */// 4. 处理并发连接情况(多个安全过程同时进行)if ((p_dev_rec->p_callback) ||(btm_cb.pairing_state != BTM_PAIR_STATE_IDLE)) {/*如果当前设备已经在处理其他安全过程(比如 pairing).或全局 pairing 状态不是空闲,就要排队。*/LOG_INFO("security_flags:x%x, sec_flags:x%x", security_required,p_dev_rec->sec_flags);rc = BTM_CMD_STARTED; // 暂时返回命令已启动(不是立刻成功)。// 判断是否满足 legacy 安全模式的要求if ((btm_cb.security_mode == BTM_SEC_MODE_SERVICE) ||(BTM_SM4_KNOWN == p_dev_rec->sm4) ||(BTM_SEC_IS_SM4(p_dev_rec->sm4) &&(!btm_sec_is_upgrade_possible(p_dev_rec, is_originator)))) {/* legacy mode - local is legacy or local is lisbon/peer is legacy* or SM4 with no possibility of link key upgrade *//*如果系统处于老版本(Legacy)安全模式或某些情况不能升级:是否需要认证?是否需要加密?如果需要 16位 PIN(强认证),也检查。简单理解: 只要满足当前安全要求(加密/认证/16位PIN),就直接返回成功,否则需要等待后续处理。*/if (is_originator) {if (((security_required & BTM_SEC_OUT_FLAGS) == 0) ||((((security_required & BTM_SEC_OUT_FLAGS) ==BTM_SEC_OUT_AUTHENTICATE) &&btm_dev_authenticated(p_dev_rec))) ||((((security_required & BTM_SEC_OUT_FLAGS) ==(BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_ENCRYPT)) &&btm_dev_encrypted(p_dev_rec)))) {rc = BTM_SUCCESS;}} else {if (((security_required & BTM_SEC_IN_FLAGS) == 0) ||(((security_required & BTM_SEC_IN_FLAGS) ==BTM_SEC_IN_AUTHENTICATE) &&btm_dev_authenticated(p_dev_rec)) ||(((security_required & BTM_SEC_IN_FLAGS) ==(BTM_SEC_IN_AUTHENTICATE | BTM_SEC_IN_ENCRYPT)) &&btm_dev_encrypted(p_dev_rec))) {// Check for 16 digits (or MITM)if (((security_required & BTM_SEC_IN_MIN_16_DIGIT_PIN) == 0) ||(((security_required & BTM_SEC_IN_MIN_16_DIGIT_PIN) ==BTM_SEC_IN_MIN_16_DIGIT_PIN) &&btm_dev_16_digit_authenticated(p_dev_rec))) {rc = BTM_SUCCESS;}}}if ((rc == BTM_SUCCESS) && (security_required & BTM_SEC_MODE4_LEVEL4) &&(p_dev_rec->link_key_type != BTM_LKEY_TYPE_AUTH_COMB_P_256)) {rc = BTM_CMD_STARTED;}if (rc == BTM_SUCCESS) {// 特别注意:临时配对的限制// 如果是临时绑定状态(例如没保存的配对信息),禁止访问高安全性服务。if (access_secure_service_from_temp_bond(p_dev_rec, is_originator, security_required)) {LOG_ERROR("Trying to access a secure service from a temp bonding, rejecting");rc = BTM_FAILED_ON_SECURITY;}if (p_callback)(*p_callback)(&bd_addr, transport, (void*)p_ref_data, rc);return (rc);}}btm_cb.sec_req_pending = true;return (BTM_CMD_STARTED);}// 记录安全请求// 如果需要等待进一步配对、加密,保存相关信息:/* Save the security requirements in case a pairing is needed */p_dev_rec->required_security_flags_for_pairing = security_required;// 根据不同的安全模式,动态调整安全要求(比如一定要加密):/* Modify security_required in btm_sec_l2cap_access_req for Lisbon */if (btm_cb.security_mode == BTM_SEC_MODE_SP ||btm_cb.security_mode == BTM_SEC_MODE_SC) {if (BTM_SEC_IS_SM4(p_dev_rec->sm4)) {if (is_originator) {/* SM4 to SM4 -> always encrypt */security_required |= BTM_SEC_OUT_ENCRYPT;} else /* acceptor */{/* SM4 to SM4: the acceptor needs to make sure the authentication is* already done */chk_acp_auth_done = true;/* SM4 to SM4 -> always encrypt */security_required |= BTM_SEC_IN_ENCRYPT;}} else if (!(BTM_SM4_KNOWN & p_dev_rec->sm4)) { // 如果对方特性未知,先等待特性交换/* the remote features are not known yet */LOG_INFO("Remote features have not yet been received sec_flags:0x%02x %s",p_dev_rec->sec_flags, (is_originator) ? "initiator" : "acceptor");p_dev_rec->sm4 |= BTM_SM4_REQ_PEND;// 如果对方设备特性(比如是否支持 SC)还不知道,先标记 pending,稍后再处理。return (BTM_CMD_STARTED); }}BTM_TRACE_DEBUG("%s() sm4:0x%x, sec_flags:0x%x, security_required:0x%x chk:%d", __func__,p_dev_rec->sm4, p_dev_rec->sec_flags, security_required,chk_acp_auth_done);// 更新设备安全记录// 保存更新后的安全要求、回调、发起方信息:p_dev_rec->security_required = security_required;p_dev_rec->p_ref_data = p_ref_data;p_dev_rec->is_originator = is_originator;if (chk_acp_auth_done) {BTM_TRACE_DEBUG("(SM4 to SM4) btm_sec_l2cap_access_req rspd. authenticated: x%x, enc: ""x%x",(p_dev_rec->sec_flags & BTM_SEC_AUTHENTICATED),(p_dev_rec->sec_flags & BTM_SEC_ENCRYPTED));/* SM4, but we do not know for sure which level of security we need.* as long as we have a link key, it's OK */if ((0 == (p_dev_rec->sec_flags & BTM_SEC_AUTHENTICATED)) ||(0 == (p_dev_rec->sec_flags & BTM_SEC_ENCRYPTED))) {rc = BTM_DELAY_CHECK;/*2046 may report HCI_Encryption_Change and L2C Connection Request out ofsequencebecause of data path issues. Delay this disconnect a little bit*/LOG_INFO("%s peer should have initiated security process by now (SM4 to SM4)",__func__);p_dev_rec->p_callback = p_callback;p_dev_rec->sec_state = BTM_SEC_STATE_DELAY_FOR_ENC;(*p_callback)(&bd_addr, transport, p_ref_data, rc);return BTM_SUCCESS;}}p_dev_rec->p_callback = p_callback;// 进一步针对 Secure Connections 校验if (BTM_SEC_IS_SM4(p_dev_rec->sm4)) {if ((p_dev_rec->security_required & BTM_SEC_MODE4_LEVEL4) &&(p_dev_rec->link_key_type != BTM_LKEY_TYPE_AUTH_COMB_P_256)) {/* BTM_LKEY_TYPE_AUTH_COMB_P_256 is the only acceptable key in this case*/if ((p_dev_rec->sec_flags & BTM_SEC_LINK_KEY_KNOWN) != 0) {p_dev_rec->sm4 |= BTM_SM4_UPGRADE; // 如果要求 Mode4 Level4,但 link key 不够强(不是 P-256),就标记要升级:}p_dev_rec->sec_flags &=~(BTM_SEC_LINK_KEY_KNOWN | BTM_SEC_LINK_KEY_AUTHED |BTM_SEC_AUTHENTICATED);BTM_TRACE_DEBUG("%s: sec_flags:0x%x", __func__, p_dev_rec->sec_flags);// 如果 link key 已经有但不够强,就清掉以前的信息,强制重新认证。} else {/* If we already have a link key to the connected peer, is it secure* enough? */btm_sec_check_upgrade(p_dev_rec, is_originator);}}// 启动安全处理流程,比如配对、加密协商等。rc = btm_sec_execute_procedure(p_dev_rec); // 在我们分析的这个案例中,在这里返回了 BTM_SUCCESSif (rc != BTM_CMD_STARTED) {// 如果不是 BTM_CMD_STARTED(比如失败了),立即回调上层。BTM_TRACE_DEBUG("%s: p_dev_rec=%p, clearing callback. old p_callback=%p",__func__, p_dev_rec, p_dev_rec->p_callback);p_dev_rec->p_callback = NULL;(*p_callback)(&bd_addr, transport, p_dev_rec->p_ref_data, rc); // 在这里触发了 l2c_link_sec_comp 调用}// 最后根据执行结果返回。return (rc);
}
这个函数做的事情可以简单理解为:
步骤 | 目的 |
---|---|
找到设备记录 | 了解设备当前的安全状态 |
检查本地和远端是否支持需要的特性 | 比如 Secure Connections |
判断现有链接是否已经足够安全 | 是否需要加密/认证/重新配对 |
根据需要触发配对、加密 | 启动安全过程或者直接回调成功 |
使用场景:
- 你手机连耳机,如果耳机要求安全连接但你手机不支持,就直接拒绝。
- 你手机和支付终端连,要求用 P-256 密钥,如果之前是旧版配对,就重新触发安全升级。
- 多个蓝牙服务(比如耳机音频+电话)同时要求连接,按顺序一个个处理安全性。
btm_sec_execute_procedure
- 传入参数是一个指向设备安全记录(
p_dev_rec
)的指针。 - 返回类型是
tBTM_STATUS
,代表操作结果,比如成功、失败、启动中等。
// system/stack/btm/btm_sec.cctBTM_STATUS btm_sec_execute_procedure(tBTM_SEC_DEV_REC* p_dev_rec) {CHECK(p_dev_rec != nullptr);// 把当前设备需要的安全要求、已有的安全状态、以及安全过程状态打日志。// 方便调试:比如知道当前是否已经加密、认证了。LOG_INFO("security_required:0x%x security_flags:0x%x security_state:%s[%hhu]",p_dev_rec->security_required, p_dev_rec->sec_flags,security_state_text(static_cast<tSECURITY_STATE>(p_dev_rec->sec_state)).c_str(),p_dev_rec->sec_state);// 1. 如果设备当前正在进行安全流程,不重复发起if (p_dev_rec->sec_state != BTM_SEC_STATE_IDLE) {// 如果设备不是空闲状态(比如正在认证或加密中),就直接返回,说明流程已经在跑了。LOG_INFO("Security state is idle indicating remote name request is outstanding");return (BTM_CMD_STARTED);}/* 2. 如果设备名字未知,先发起远程名字请求1. 为什么要名字?:很多安全策略需要知道对方设备名字(比如判断是已知设备还是陌生设备)。2. 如果名字没拿到,先请求获取远程设备名字。*//* If any security is required, get the name first */if (!(p_dev_rec->sec_flags & BTM_SEC_NAME_KNOWN) &&(p_dev_rec->hci_handle != HCI_INVALID_HANDLE)) {LOG_INFO("Security Manager: Start get name");if (!btm_sec_start_get_name(p_dev_rec)) {LOG_WARN("Unable to start remote name request");return (BTM_NO_RESOURCES);}return (BTM_CMD_STARTED);}/* If connection is not authenticated and authentication is required *//* start authentication and return PENDING to the caller */// 3. 检查是否需要认证(配对)if (p_dev_rec->hci_handle != HCI_INVALID_HANDLE) {// 连接已经建立(hci_handle有效),进入检查阶段。bool start_auth = false;// Check link status of BR/EDR// 3.1 出站 or 入站认证需求判断// 如果还没认证,就根据连接方向(自己发起or对方发起)判断要不要启动认证。// 出站连接看 BTM_SEC_OUT_AUTHENTICATE// 入站连接看 BTM_SEC_IN_AUTHENTICATEif (!(p_dev_rec->sec_flags & BTM_SEC_AUTHENTICATED)) {if (p_dev_rec->IsLocallyInitiated()) {if (p_dev_rec->security_required &(BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_ENCRYPT)) {LOG_INFO("Outgoing authentication/encryption Required");start_auth = true;}} else {if (p_dev_rec->security_required &(BTM_SEC_IN_AUTHENTICATE | BTM_SEC_IN_ENCRYPT)) {LOG_INFO("Incoming authentication/encryption Required");start_auth = true;}}}// 3.2 检查是否需要16位PIN码if (!(p_dev_rec->sec_flags & BTM_SEC_16_DIGIT_PIN_AUTHED)) {/** We rely on BTM_SEC_16_DIGIT_PIN_AUTHED being set if MITM is in use,* as 16 DIGIT is only needed if MITM is not used. Unfortunately, the* BTM_SEC_AUTHENTICATED is used for both MITM and non-MITM* authenticated connections, hence we cannot distinguish here.*/// 如果没有16位PIN码认证,但要求了 BTM_SEC_IN_MIN_16_DIGIT_PIN,则也要认证,这个通常用于防止弱PIN码攻击。if (!p_dev_rec->IsLocallyInitiated()) {if (p_dev_rec->security_required & BTM_SEC_IN_MIN_16_DIGIT_PIN) {LOG_INFO("BTM_SEC_IN_MIN_16_DIGIT_PIN Required");start_auth = true;}}}// 3.3 如果需要认证,则启动认证流程if (start_auth) {LOG_INFO("Security Manager: Start authentication");/** If we do have a link-key, but we end up here because we need an* upgrade, then clear the link-key known and authenticated flag before* restarting authentication.* WARNING: If the controller has link-key, it is optional and* recommended for the controller to send a Link_Key_Request.* In case we need an upgrade, the only alternative would be to delete* the existing link-key. That could lead to very bad user experience* or even IOP issues, if a reconnect causes a new connection that* requires an upgrade.*/// 特殊情况:需要升级链接密钥if ((p_dev_rec->sec_flags & BTM_SEC_LINK_KEY_KNOWN) &&(!(p_dev_rec->sec_flags & BTM_SEC_16_DIGIT_PIN_AUTHED) &&(!p_dev_rec->IsLocallyInitiated() &&(p_dev_rec->security_required & BTM_SEC_IN_MIN_16_DIGIT_PIN)))) {// 如果已经有密钥,但不满足更高安全要求(比如16位PIN),就清除旧的密钥标记,强制重做配对。这是为了安全升级。p_dev_rec->sec_flags &=~(BTM_SEC_LINK_KEY_KNOWN | BTM_SEC_LINK_KEY_AUTHED |BTM_SEC_AUTHENTICATED);}// 真正启动认证btm_sec_wait_and_start_authentication(p_dev_rec); // 启动认证(配对)流程return (BTM_CMD_STARTED); // 返回正在进行中}}/* If connection is not encrypted and encryption is required *//* start encryption and return PENDING to the caller */// 4. 检查是否需要加密if (!(p_dev_rec->sec_flags & BTM_SEC_ENCRYPTED) &&((p_dev_rec->IsLocallyInitiated() &&(p_dev_rec->security_required & BTM_SEC_OUT_ENCRYPT)) ||(!p_dev_rec->IsLocallyInitiated() &&(p_dev_rec->security_required & BTM_SEC_IN_ENCRYPT))) &&(p_dev_rec->hci_handle != HCI_INVALID_HANDLE)) {// 如果还没有加密,且上层要求了加密,则发起加密。BTM_TRACE_EVENT("Security Manager: Start encryption");btsnd_hcic_set_conn_encrypt(p_dev_rec->hci_handle, true); // 使用 HCI_Set_Connection_Encryption 指令。p_dev_rec->sec_state = BTM_SEC_STATE_ENCRYPTING; // 状态设为 ENCRYPTING,返回处理中return (BTM_CMD_STARTED);} else {LOG_INFO("Encryption not required");}// 5. 检查是否符合 Level 4 安全要求(SC Only)if ((p_dev_rec->security_required & BTM_SEC_MODE4_LEVEL4) &&(p_dev_rec->link_key_type != BTM_LKEY_TYPE_AUTH_COMB_P_256)) {// 如果要求 Level 4,但连接密钥不是 P-256 生成的(老的密钥),直接失败。// 保护:防止旧的不安全链接偷渡。BTM_TRACE_EVENT("%s: Security Manager: SC only service, but link key type is 0x%02x -","security failure", __func__, p_dev_rec->link_key_type);return (BTM_FAILED_ON_SECURITY);}// 6. 检查是否是"临时绑定"设备在访问安全服务if (access_secure_service_from_temp_bond(p_dev_rec,p_dev_rec->IsLocallyInitiated(),p_dev_rec->security_required)) {LOG_ERROR("Trying to access a secure service from a temp bonding, rejecting");// 如果是临时配对的(没正式保存密钥),而试图访问高安全服务,拒绝!return (BTM_FAILED_ON_SECURITY);}/* All required security procedures already established */// 所有检查通过,清理安全请求标志p_dev_rec->security_required &=~(BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_IN_AUTHENTICATE |BTM_SEC_OUT_ENCRYPT | BTM_SEC_IN_ENCRYPT);BTM_TRACE_EVENT("Security Manager: access granted");// 安全检查通过,允许访问return (BTM_SUCCESS); // 返回成功,可以正式使用连接了。
}
这段 btm_sec_execute_procedure
做了这些事情:
-
看状态:是不是空闲?是不是已经有名字?是不是认证了?是不是加密了?
-
做必要动作:如果没有名字就拿名字,没认证就认证,没加密就加密。
-
符合安全级别要求吗:比如 Level 4,SC Only。
-
临时配对限制:临时配对不能访问高安全服务。
-
最终决定:可以访问(成功)或安全失败。
它是蓝牙设备连接过程中,真正落地执行安全控制策略的地方!
5. 总结
车机接收到 手机侧的 avdtp 连接请求后:
- 通过 l2cu_find_rcb_by_psm 传入 psm, 找到对应的服务。 这里找到了我们之前蓝牙初始化注册进 l2cap 的 avdtp 服务。
- 通过 l2cu_allocate_ccb 分配 一个 Channel Control Block ,并且将 该通道控制块的状态设置为 CST_CLOSED。 这是该通道的初始状态。
- 通过 l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info), 触发状态机处理 L2CEVT_L2CAP_CONNECT_REQ 事件; p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; 将状态切换到 “等待安全完成”
- 本地开始对应的安全检查。
- 安全检查通过后,回调 l2c_link_sec_comp 函数。
2. 车机同意连接
1. 日志
1082 2025-04-24 15:56:22.505074 22:22:96:de:b1:39 (leo 8295 chan) vivoMobi_91:b0:62 (cbx) L2CAP 21 Sent Connection Response - Success (SCID: 0x0041, DCID: 0x0046)Frame 1082: 21 bytes on wire (168 bits), 21 bytes captured (168 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 12CID: L2CAP Signaling Channel (0x0001)Command: Connection ResponseCommand Code: Connection Response (0x03)Command Identifier: 0x11Command Length: 8Destination CID: Dynamically Allocated Channel (0x0046)Source CID: Dynamically Allocated Channel (0x0041)Result: Successful (0x0000)Status: No further information available (0x0000)
# 安全模块 检查完毕
04-24 15:56:22.504876 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: Security Manager: access granted04-24 15:56:22.504883 6130 6190 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_sec_l2cap_access_req_by_requirement: p_dev_rec=0xb800004b18420208, clearing callback. old p_callback=0x7a3fcaf9cc# 回调 l2c_link_sec_comp
04-24 15:56:22.504893 6130 6190 I l2c_link: packages/modules/Bluetooth/system/stack/l2cap/l2c_link.cc:284 l2c_link_sec_comp2: btm_status=BTM_SUCCESS, BD_ADDR=xx:xx:xx:xx:b0:62, transport=BT_TRANSPORT_BR_EDR
04-24 15:56:22.504901 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_TERM_W4_SEC_COMP [2], event=SECURITY_COMPLETE [7]
我们从 回调 l2c_link_sec_comp 开始梳理。
2. 源码分析
1. l2c_link_sec_comp2
l2c_link_sec_comp2
函数是 L2CAP 层在接收到 Security Manager(安全管理模块) 回调(Security Completion)之后,根据安全认证结果,继续推进连接流程的一个重要处理函数
参数:
-
p_bda:远端设备的蓝牙地址(谁的安全过程完成了)
-
transport:传输类型(BR/EDR 或 LE);这里没用到(标记
UNUSED_ATTR
),但是为了接口统一保留 -
p_ref_data:指向发起安全检查请求时附带的指针,这里是指向对应的 L2CAP CCB (Channel Control Block)
-
status:安全检查(如鉴权、加密等)完成后的结果,比如
BTM_SUCCESS
、BTM_AUTH_FAILURE
等
// system/stack/l2cap/l2c_link.cc
void l2c_link_sec_comp(const RawAddress* p_bda,UNUSED_ATTR tBT_TRANSPORT transport, void* p_ref_data,tBTM_STATUS status) {l2c_link_sec_comp2(*p_bda, transport, p_ref_data, status);
}void l2c_link_sec_comp2(const RawAddress& p_bda,UNUSED_ATTR tBT_TRANSPORT transport, void* p_ref_data,tBTM_STATUS status) {tL2C_CONN_INFO ci; // 连接信息结构体,用于后续封装事件参数。tL2C_LCB* p_lcb; // Link Control Block,连接控制块,代表一条逻辑连接。tL2C_CCB* p_ccb; // Channel Control Block,信道控制块,代表一条具体的信道。tL2C_CCB* p_next_ccb; // Channel Control Block,信道控制块,代表一条具体的信道。// 打印日志,显示安全过程返回的状态(status)、设备地址(p_bda)、传输类型(BR/EDR or LE)。LOG_INFO("btm_status=%s, BD_ADDR=%s, transport=%s",btm_status_text(status).c_str(), PRIVATE_ADDRESS(p_bda),bt_transport_text(transport).c_str());// 特殊处理:if (status == BTM_SUCCESS_NO_SECURITY) {// 如果安全管理器返回 BTM_SUCCESS_NO_SECURITY(即“不需要安全”也算通过),直接将其视为普通的 BTM_SUCCESS// 统一后续处理逻辑status = BTM_SUCCESS;}// 把当前设备地址和安全检查结果打包到 ci,准备给后续状态机(l2c_csm_execute)使用/* Save the parameters */ci.status = status;ci.bd_addr = p_bda;// 根据设备地址和传输类型,找到对应的 连接控制块(LCB), LCB 代表当前设备和对方设备之间的“链接”p_lcb = l2cu_find_lcb_by_bd_addr(p_bda, transport);/* If we don't have one, this is an error */if (!p_lcb) {// 如果找不到对应的 LCB// 说明出现了异常,比如链接已经断了,但安全过程回调还到达了;LOG_WARN("L2CAP got sec_comp for unknown BD_ADDR");// 打印警告日志,直接返回,不做任何处理。return;}/* Match p_ccb with p_ref_data returned by sec manager */for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) {// 遍历当前连接(LCB)上挂着的所有 CCB(信道)。// 每一个 CCB 对应一个 L2CAP Channel(如 SDP、RFCOMM、AVCTP、A2DP 等通道)p_next_ccb = p_ccb->p_next_ccb;// p_next_ccb 保存下一个,防止遍历时链表出错。// 找到和回调参数 p_ref_data 匹配的 CCB。// 这里要求精确匹配指针,所以安全回调回来时能直接定位到是哪一个信道触发的安全流程。if (p_ccb == p_ref_data) {switch (status) {// 如果安全认证成功:case BTM_SUCCESS:// 触发 CCB 的状态机,发送 L2CEVT_SEC_COMP 事件,带着连接信息(ci)l2c_csm_execute(p_ccb, L2CEVT_SEC_COMP, &ci); // break;case BTM_DELAY_CHECK:// 意思是要延迟一段时间再确认安全性(SM4 协议相关,旧版蓝牙的一种安全模式)/* start a timer - encryption change not received before L2CAP connect* req */// 启动一个定时器,等超时后重新处理。alarm_set_on_mloop(p_ccb->l2c_ccb_timer,L2CAP_DELAY_CHECK_SM4_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb);return; // 注意这里直接 return,不继续遍历default:// 其他情况(比如认证失败、加密失败等)// 触发 CCB 状态机,发送 L2CEVT_SEC_COMP_NEG 事件// 通常最终导致连接失败或断开l2c_csm_execute(p_ccb, L2CEVT_SEC_COMP_NEG, &ci);break;}break; // 处理完对应的 CCB后,直接退出循环,不再继续找其他信道}}
}
- L2CAP在安全认证(比如加密或鉴权)完成后,根据认证结果,通知对应的信道状态机继续连接流程或者终止连接。
如果认证成功,就继续发起连接;如果失败,就中断连接。 - 正常情况下, 会返回 BTM_SUCCESS
- l2c_csm_execute(p_ccb, L2CEVT_SEC_COMP, &ci)
我们继续分析 l2c_csm_execute 的执行:
- 此时 p_ccb->chnl_state = CST_TERM_W4_SEC_COMP
- event = L2CEVT_SEC_COMP
04-24 15:56:22.504901 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_TERM_W4_SEC_COMP [2], event=SECURITY_COMPLETE [7]
void l2c_csm_execute(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {if (p_ccb == nullptr) {LOG_WARN("CCB is null for event (%d)", event);return;}LOG_INFO("Entry chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);switch (p_ccb->chnl_state) {case CST_TERM_W4_SEC_COMP:l2c_csm_term_w4_sec_comp(p_ccb, event, p_data);break;}}
2. l2c_csm_term_w4_sec_comp
这段代码是 L2CAP CCB(Channel Control Block)状态机中,在"终止中等待安全认证完成"状态(TERM_W4_SEC_COMP)下处理各种事件的核心逻辑。
- 函数定义:处理 L2CAP CCB 当前处于 CST_TERM_W4_SEC_COMP 状态时收到的各种事件。
p_ccb
:对应的 Channel Control Block。event
:收到的事件类型(如连接请求、断开指示、安全认证完成等)。p_data
:事件附带的参数(类型根据不同事件不同)。
04-24 15:56:22.504907 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:481 l2c_csm_term_w4_sec_comp: LCID: 0x0046 st: TERM_W4_SEC_COMP evt: SECURITY_COMPLETE04-24 15:56:22.504913 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:497 l2c_csm_term_w4_sec_comp: Not waiting for info response, sending connect response04-24 15:56:22.504922 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:503 l2c_csm_term_w4_sec_comp: Not LE connection, sending configure request
static void l2c_csm_term_w4_sec_comp(tL2C_CCB* p_ccb, tL2CEVT event,void* p_data) {// 打日志:记录当前 Local CID、本状态名、接收到的事件名,便于调试。LOG_INFO("LCID: 0x%04x st: TERM_W4_SEC_COMP evt: %s", p_ccb->local_cid,l2c_csm_get_event_name(event));switch (event) {case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */// 链路断开通知(Link Layer)。链路断了,当前还在等待安全认证,就没必要继续了。/* Tell security manager to abort */btm_sec_abort_access_req(p_ccb->p_lcb->remote_bd_addr); // 通知 Security Manager 中止本次安全请求。l2cu_release_ccb(p_ccb); // 释放掉当前的 Channel Control Block(内存、资源等都回收)break;case L2CEVT_SEC_COMP:// 安全认证完成并且成功。p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP; // 把 Channel 状态转为 等待上层(L2CA)回应连接。/* Wait for the info resp in next state before sending connect ind (if* needed) */if (!p_ccb->p_lcb->w4_info_rsp) {// 如果 不需要等待 Info Response,直接继续连接流程// Info Response 是标准 L2CAP连接时用来了解对方特性的一步。LOG_INFO("Not waiting for info response, sending connect response");/* Don't need to get info from peer or already retrieved so continue */alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 开启连接超时定时器if (p_ccb->p_lcb->transport != BT_TRANSPORT_LE) {// 如果是经典蓝牙(BR/EDR)连接:LOG_INFO("Not LE connection, sending configure request");l2c_csm_send_connect_rsp(p_ccb); // 发送 Connect Responsel2c_csm_send_config_req(p_ccb); // 发送 Configuration Request(即开始配置通道参数)} else {// 如果是 BLE 连接(Bluetooth Low Energy)if (p_ccb->ecoc) { // 是 Enhanced Credit Based Connection (ECoC)// 使用 Credit-based 多通道连接(LE ECoC),一次可以连多个 CID/* Handle Credit Based Connection */LOG_INFO("Calling CreditBasedConnect_Ind_Cb(), num of cids: %d",p_ccb->p_lcb->pending_ecoc_conn_cnt);std::vector<uint16_t> pending_cids;for (int i = 0; i < p_ccb->p_lcb->pending_ecoc_conn_cnt; i++) {// 把要建立连接的所有 pending CIDs 收集起来。uint16_t cid = p_ccb->p_lcb->pending_ecoc_connection_cids[i];if (cid != 0) pending_cids.push_back(cid);}// 通知上层 Profile,有多个 CID 等待建立。(*p_ccb->p_rcb->api.pL2CA_CreditBasedConnectInd_Cb)(p_ccb->p_lcb->remote_bd_addr, pending_cids, p_ccb->p_rcb->psm,p_ccb->peer_conn_cfg.mtu, p_ccb->remote_id);} else {// 否则是传统 BLE CoC, 使用传统 BLE CoC(Credit-Based Connection,单一通道)。/* Handle BLE CoC */LOG_INFO("Calling Connect_Ind_Cb(), CID: 0x%04x",p_ccb->local_cid);l2c_csm_send_connect_rsp(p_ccb); // 回复 connect rsp。l2c_csm_indicate_connection_open(p_ccb); // 告知上层连接已经建立成功。}}} else { // 如果还在等 Info Response// 等待对方的 Info Response,比如设备特性等。// 注意到这里讲了蓝牙兼容性问题(Bluesoleil响应慢的问题)。/*** L2CAP Connect Response will be sent out by 3 sec timer expiration** because Bluesoleil doesn't respond to L2CAP Information Request.** Bluesoleil seems to disconnect ACL link as failure case, because** it takes too long (4~7secs) to get response.** product version : Bluesoleil 2.1.1.0 EDR Release 060123** stack version : 05.04.11.20060119*//* Cancel ccb timer as security complete. waiting for w4_info_rsp** once info rsp received, connection rsp timer will be started** while sending connection ind to profiles*/alarm_cancel(p_ccb->l2c_ccb_timer); // 取消之前的定时器。/* Waiting for the info resp, tell the peer to set a longer timer */LOG_INFO("Waiting for info response, sending connect pending");l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0); // 向对方回复 "连接还在处理中"(PENDING 状态),以防超时。}break;case L2CEVT_SEC_COMP_NEG:// 安全认证失败if (((tL2C_CONN_INFO*)p_data)->status == BTM_DELAY_CHECK) {// 特殊情况 BTM_DELAY_CHECK// 如果失败原因是 "需要延迟检查"(通常是SM4安全模式下的一种处理)。/* start a timer - encryption change not received before L2CAP connect* req */alarm_set_on_mloop(p_ccb->l2c_ccb_timer,L2CAP_DELAY_CHECK_SM4_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 启动延迟定时器,后续补发认证。} else {// 其他认证失败情况if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE)l2cu_reject_ble_connection(p_ccb, p_ccb->remote_id,L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION); // 如果是 BLE,发 BLE 的拒绝连接(错误码:认证失败)。elsel2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_SECURITY_BLOCK, 0); // 如果是 BR/EDR,发送 L2CAP 层的拒绝(SECURITY BLOCK)。l2cu_release_ccb(p_ccb); // 最后释放掉这个 CCB。}break;case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */// 有数据写入或收到数据,但当前正在断开中,丢弃数据并释放。osi_free(p_data);break;case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */// 上层主动要求断开,直接释放通道。l2cu_release_ccb(p_ccb);break;case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */// 对方发起断开。l2cu_send_peer_disc_rsp(p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid,p_ccb->remote_cid); // 回复 disconnect response。/* Tell security manager to abort */btm_sec_abort_access_req(p_ccb->p_lcb->remote_bd_addr); //通知 Security Manager 取消认证l2cu_release_ccb(p_ccb); // 释放通道break;case L2CEVT_TIMEOUT:// 超时,比如认证超时了,主动断 ACL 链路。/* SM4 related. */acl_disconnect_from_handle(p_ccb->p_lcb->Handle(), HCI_ERR_AUTH_FAILURE,"stack::l2cap::l2c_csm::l2c_csm_term_w4_sec_comp Event timeout");break;case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */// 安全模块通知可以重新发起安全请求,比如密钥刷新之类的。btm_sec_l2cap_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm,false, &l2c_link_sec_comp, p_ccb); // 再次请求访问权限。break;default:// 遇到未处理的事件,打印错误日志。LOG_ERROR("Handling unexpected event:%s", l2c_csm_get_event_name(event));}LOG_INFO("Exit chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);
}
这段 l2c_csm_term_w4_sec_comp
的本质是:
- 在等待安全认证的时候,处理各种意外情况(断链、认证失败、认证成功、超时等)。
- 保证:
- 正常认证成功时,能继续连接流程;
- 认证失败或断链时,能正确清理资源;
- 防止泄露或死锁。
目前我们只需要关注 L2CEVT_SEC_COMP认证成功的即可
- p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP : 把 Channel 状态转为 等待上层(L2CA)回应连接
- l2c_csm_send_connect_rsp(p_ccb) :发送 Connect Response
- l2c_csm_send_config_req(p_ccb) : 发送 Configuration Request(即开始配置通道参数)
3. l2c_csm_send_connect_rsp
04-24 15:56:22.504928 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_W4_L2CA_CONNECT_RSP [4], event=UPPER_LAYER_CONNECT_RSP [22]04-24 15:56:22.504935 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:810 l2c_csm_w4_l2ca_connect_rsp: LCID: 0x0046 st: W4_L2CA_CON_RSP evt: UPPER_LAYER_CONNECT_RSP04-24 15:56:22.504941 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:870 l2c_csm_w4_l2ca_connect_rsp: Sending connection ok for BR_EDR
static void l2c_csm_send_connect_rsp(tL2C_CCB* p_ccb) {l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL); // 发送 L2CEVT_L2CA_CONNECT_RSP 事件
}void l2c_csm_execute(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {if (p_ccb == nullptr) {LOG_WARN("CCB is null for event (%d)", event);return;}LOG_INFO("Entry chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);switch (p_ccb->chnl_state) {case CST_W4_L2CA_CONNECT_RSP:l2c_csm_w4_l2ca_connect_rsp(p_ccb, event, p_data);break;break;}}
4. l2c_csm_w4_l2ca_connect_rsp
-
专门处理 CCB(Channel Control Block)在等待上层回应连接请求(Connect Response)阶段时发生的各种事件
-
p_ccb
:当前信道的控制块(Channel Control Block),管理一个逻辑信道(LCID)。 -
event
:本次触发状态机的事件类型(枚举)。 -
p_data
:附加数据(根据不同事件类型,结构体不同,比如tL2C_CONN_INFO
等)。
// system/stack/l2cap/l2c_csm.cc
static void l2c_csm_w4_l2ca_connect_rsp(tL2C_CCB* p_ccb, tL2CEVT event,void* p_data) {tL2C_CONN_INFO* p_ci; // 指向连接信息,后面处理连接响应用。tL2C_LCB* p_lcb = p_ccb->p_lcb; // 指向对应的 LCB(Link Control Block,表示物理链接,例如 BR/EDR 或 LE 链路)。tL2CA_DISCONNECT_IND_CB* disconnect_ind =p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; // 上层注册的断开连接回调函数。uint16_t local_cid = p_ccb->local_cid; // 本地的 CID(Channel ID),用于标识逻辑信道LOG_INFO("LCID: 0x%04x st: W4_L2CA_CON_RSP evt: %s", p_ccb->local_cid,l2c_csm_get_event_name(event)); // 日志,打印当前 Local CID、状态(等待上层连接响应 W4_L2CA_CON_RSP)和触发事件名。switch (event) {// 1. 链路层断开通知case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */// 链路断开了(可能是对端断开或者链路异常中断)。LOG_INFO("Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb); // 释放 p_ccb(*disconnect_ind)(local_cid, false); // 调用上层回调 disconnect_ind() 通知上层,不需要等待 Disconnect Confirmbreak;case L2CEVT_L2CA_CREDIT_BASED_CONNECT_RSP: // CREDIT_BASED 连接回应// 收到上层对 Credit-Based 连接请求的响应(通常是 LE 上的信用基连接)。p_ci = (tL2C_CONN_INFO*)p_data;if ((p_lcb == nullptr) || (p_lcb && p_lcb->transport != BT_TRANSPORT_LE)) {// 检查链路是否存在且是 LE 链路。LOG_WARN("LE link doesn't exist");return;}l2cu_send_peer_credit_based_conn_res(p_ccb, p_ci->lcids,p_ci->l2cap_result); // 回复对端连接结果alarm_cancel(p_ccb->l2c_ccb_timer); // 停止 CCB 上的连接超时定时器// 遍历 pending_ecoc_connection_cids 数组,设置对应 CCB 的状态:for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) {uint16_t cid = p_lcb->pending_ecoc_connection_cids[i];if (cid == 0) {LOG_WARN("pending_ecoc_connection_cids[%d] is %d", i, cid);continue;}tL2C_CCB* temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid);if (temp_p_ccb) {auto it = std::find(p_ci->lcids.begin(), p_ci->lcids.end(), cid);if (it != p_ci->lcids.end()) {// 成功的 CID,状态设为 CST_OPEN(已连接)temp_p_ccb->chnl_state = CST_OPEN;} else {l2cu_release_ccb(temp_p_ccb); // 失败的,释放 CCB。}}else {LOG_WARN("temp_p_ccb is NULL, pending_ecoc_connection_cids[%d] is %d", i, cid);}}// 清空 pending_ecoc_connection_cids 相关字段p_lcb->pending_ecoc_conn_cnt = 0;memset(p_lcb->pending_ecoc_connection_cids, 0,L2CAP_CREDIT_BASED_MAX_CIDS);break;// 经典连接回应case L2CEVT_L2CA_CONNECT_RSP:// 收到上层对传统连接请求(BR/EDR 或 LE)的响应。p_ci = (tL2C_CONN_INFO*)p_data;if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) {// LE (信用基连接)/* Result should be OK or Reject */if ((!p_ci) || (p_ci->l2cap_result == L2CAP_CONN_OK)) {// 如果上层同意(l2cap_result == L2CAP_CONN_OK),发送 OK 响应,状态设 CST_OPENl2cble_credit_based_conn_res(p_ccb, L2CAP_CONN_OK);p_ccb->chnl_state = CST_OPEN;alarm_cancel(p_ccb->l2c_ccb_timer);} else {// 否则发送拒绝并释放 CCB。l2cble_credit_based_conn_res(p_ccb, p_ci->l2cap_result);l2cu_release_ccb(p_ccb);}} else {// BR/EDR (传统)/* Result should be OK or PENDING */if ((!p_ci) || (p_ci->l2cap_result == L2CAP_CONN_OK)) {// 上层同意(l2cap_result == OK),发送 OK,转到 CST_CONFIG(配置阶段),开启配置超时定时器。LOG_INFO("Sending connection ok for BR_EDR");l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_OK, 0);p_ccb->chnl_state = CST_CONFIG;alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb);} else {// 上层暂时挂起(Pending),发送 PENDING,开启扩展连接超时定时器。/* If pending, stay in same state and start extended timer */LOG_INFO("Sending connection result %d and status %d",p_ci->l2cap_result, p_ci->l2cap_status);l2cu_send_peer_connect_rsp(p_ccb, p_ci->l2cap_result,p_ci->l2cap_status);alarm_set_on_mloop(p_ccb->l2c_ccb_timer,L2CAP_CHNL_CONNECT_EXT_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb);}}break;case L2CEVT_L2CA_CREDIT_BASED_CONNECT_RSP_NEG:// 上层拒绝 Credit-Based 连接请求。p_ci = (tL2C_CONN_INFO*)p_data;alarm_cancel(p_ccb->l2c_ccb_timer); // 停掉定时器。if (p_lcb != nullptr) {if (p_lcb->transport == BT_TRANSPORT_LE) {// 如果链路存在且是 LE,发送拒绝回应l2cu_send_peer_credit_based_conn_res(p_ccb, p_ci->lcids,p_ci->l2cap_result);}for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) {uint16_t cid = p_lcb->pending_ecoc_connection_cids[i];tL2C_CCB* temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid);l2cu_release_ccb(temp_p_ccb); // 释放所有 pending CCB。}p_lcb->pending_ecoc_conn_cnt = 0;memset(p_lcb->pending_ecoc_connection_cids, 0,L2CAP_CREDIT_BASED_MAX_CIDS);}break;case L2CEVT_L2CA_CONNECT_RSP_NEG:// 传统连接拒绝p_ci = (tL2C_CONN_INFO*)p_data;// 根据链路类型(LE or BR/EDR)发送拒绝。if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE)l2cble_credit_based_conn_res(p_ccb, p_ci->l2cap_result);elsel2cu_send_peer_connect_rsp(p_ccb, p_ci->l2cap_result,p_ci->l2cap_status);l2cu_release_ccb(p_ccb); // 然后释放 CCB。break;// 超时事件case L2CEVT_TIMEOUT:// 在等待上层响应期间超时。l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_NO_PSM, 0); // 向对端回复 NO_PSM 错误(表明不支持)。LOG_INFO("Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb); // 释放 CCB。(*disconnect_ind)(local_cid, false); // 调用上层断开通知(不需要等待确认)break;// 数据相关事件case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */osi_free(p_data); // 在连接未完成阶段,不应该有数据收发,所以这里直接 free 掉。break;// 上层请求主动断开case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */l2cu_send_peer_disc_req(p_ccb); // 发送 Disconnect Request 给对端, 状态机切换到 CST_W4_L2CAP_DISCONNECT_RSP(等待断开回应)。p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP;alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 开启断开超时定时器break;case L2CEVT_L2CAP_INFO_RSP: // 收到信息回应// 收到 L2CAP 信息响应(比如询问对端的特性)。/* We have feature info, so now give the upper layer connect IND */alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CONNECT_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 重新开启连接超时定时器。LOG_INFO("Calling Connect_Ind_Cb(), CID: 0x%04x", p_ccb->local_cid);l2c_csm_send_connect_rsp(p_ccb); // 调用 l2c_csm_send_connect_rsp(p_ccb) 给上层l2c_csm_send_config_req(p_ccb); // 调用 l2c_csm_send_config_req(p_ccb) 向对端发配置请求。break;default:// 收到不符合当前状态的事件,打错误日志。LOG_ERROR("Handling unexpected event:%s", l2c_csm_get_event_name(event));}LOG_INFO("Exit chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);
}
主要事件 | 主要处理逻辑 |
---|---|
链路断开 | 释放 CCB,通知上层。 |
上层 Credit-based Connect 回复 | 回复对端,调整信道状态。 |
上层传统 Connect 回复 | 回复对端,切换到配置阶段或超时。 |
上层拒绝连接 | 回复对端,释放资源。 |
超时 | 回复对端错误,释放资源,通知上层。 |
数据写或收 | 丢弃。 |
上层请求断开 | 发送断开请求,等待响应。 |
收到信息回应 | 继续完成连接建立。 |
我们这里只需要关注 L2CEVT_L2CA_CONNECT_RSP 事件的处理:
- l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_OK, 0);
- p_ccb->chnl_state = CST_CONFIG;
04-24 15:56:22.504928 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_W4_L2CA_CONNECT_RSP [4], event=UPPER_LAYER_CONNECT_RSP [22]04-24 15:56:22.504935 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:810 l2c_csm_w4_l2ca_connect_rsp: LCID: 0x0046 st: W4_L2CA_CON_RSP evt: UPPER_LAYER_CONNECT_RSP04-24 15:56:22.504941 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:870 l2c_csm_w4_l2ca_connect_rsp: Sending connection ok for BR_EDR
这里就是 本节 车机回复同意连接 ok , 的地方。 我们可以对照 btsnoop 日志看一下。
1082 2025-04-24 15:56:22.505074 22:22:96:de:b1:39 (leo 8295 chan) vivoMobi_91:b0:62 (cbx) L2CAP 21 Sent Connection Response - Success (SCID: 0x0041, DCID: 0x0046)Frame 1082: 21 bytes on wire (168 bits), 21 bytes captured (168 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 12CID: L2CAP Signaling Channel (0x0001)Command: Connection ResponseCommand Code: Connection Response (0x03)Command Identifier: 0x11Command Length: 8Destination CID: Dynamically Allocated Channel (0x0046)Source CID: Dynamically Allocated Channel (0x0041)Result: Successful (0x0000)Status: No further information available (0x0000)
3. 车机发起配置请求
1083 2025-04-24 15:56:22.505308 22:22:96:de:b1:39 (leo 8295 chan) vivoMobi_91:b0:62 (cbx) L2CAP 21 Sent Configure Request (DCID: 0x0041)Frame 1083: 21 bytes on wire (168 bits), 21 bytes captured (168 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 12CID: L2CAP Signaling Channel (0x0001)Command: Configure RequestCommand Code: Configure Request (0x04)Command Identifier: 0x0eCommand Length: 8Destination CID: Dynamically Allocated Channel (0x0041)0000 0000 0000 000. = Reserved: 0x0000.... .... .... ...0 = Continuation Flag: FalseOption: MTU
在 l2c_csm_term_w4_sec_comp 函数的分析中:
- p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP : 把 Channel 状态转为 等待上层(L2CA)回应连接
- l2c_csm_send_connect_rsp(p_ccb) :发送 Connect Response
- p_ccb->chnl_state = CST_CONFIG;
- l2c_csm_send_config_req(p_ccb) : 发送 Configuration Request(即开始配置通道参数)
触发了 l2c_csm_send_config_req, 本节就来分析这个。
1. l2c_csm_send_config_req
static void l2c_csm_send_config_req(tL2C_CCB* p_ccb) {tL2CAP_CFG_INFO config{};config.mtu_present = true;config.mtu = p_ccb->p_rcb->my_mtu;p_ccb->max_rx_mtu = config.mtu;if (p_ccb->p_rcb->ertm_info.preferred_mode != L2CAP_FCR_BASIC_MODE) {config.fcr_present = true;config.fcr = kDefaultErtmOptions;}p_ccb->our_cfg = config;l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONFIG_REQ, &config); // 这里发送了 L2CEVT_L2CA_CONFIG_REQ 消息
}
void l2c_csm_execute(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {if (p_ccb == nullptr) {LOG_WARN("CCB is null for event (%d)", event);return;}LOG_INFO("Entry chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);switch (p_ccb->chnl_state) {case CST_CONFIG:l2c_csm_config(p_ccb, event, p_data);break;break;}}
04-24 15:56:22.505097 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_CONFIG [5], event=UPPER_LAYER_CONFIG_REQ [24]04-24 15:56:22.505104 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:975 l2c_csm_config: LCID: 0x0046 st: CONFIG evt: UPPER_LAYER_CONFIG_REQ04-24 15:56:22.505267 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:1235 l2c_csm_config: Exit chnl_state=CST_CONFIG [5], event=UPPER_LAYER_CONFIG_REQ [24]
2. l2c_csm_config
- 信道已经进入"正在配置"阶段(CONFIG状态),不同的事件触发不同的逻辑处理。
static void l2c_csm_config(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {tL2CAP_CFG_INFO* p_cfg = (tL2CAP_CFG_INFO*)p_data; // 把传进来的 p_data 强制转换成普通L2CAP配置结构体。tL2CA_DISCONNECT_IND_CB* disconnect_ind =p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; // 保存上层应用注册的“断开指示”回调函数指针,后面如果要通知上层断开连接,就直接用这个回调。uint16_t local_cid = p_ccb->local_cid; // 本地通道号保存一份副本,后续打印日志或传递参数方便。uint8_t cfg_result; // 用于保存peer配置请求处理后的结果,比如"成功"、"需要断开"、"需要重新配置"。tL2C_LCB* p_lcb = p_ccb->p_lcb; // 链接控制块(LCB),描述物理连接(ACL link)状态。tL2C_CCB* temp_p_ccb; // 临时的CCB指针,目前还没用到。tL2CAP_LE_CFG_INFO* p_le_cfg = (tL2CAP_LE_CFG_INFO*)p_data; // 如果是LE credit-based flow,会用到这个类型(区别于经典的L2CAP)。 处理LE Credit-Based的Reconfig消息LOG_INFO("LCID: 0x%04x st: CONFIG evt: %s", p_ccb->local_cid,l2c_csm_get_event_name(event)); // 打印出当前信道ID、当前状态(CONFIG)、接收到的事件名。switch (event) {case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */// 链路层提示说物理链路断开了(ACL link down了)。 比如手机蓝牙突然关闭,车机收到链路断开的通知,就走这里LOG_INFO("Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb); // 释放CCB(信道控制块),因为连接没了;(*disconnect_ind)(local_cid, false); // 通知上层连接断开,参数 false 表示“不需要上层回复Confirm”。break;case L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_REQ:// 接收到Peer设备发来的LE Credit-Based重配置请求。比如耳机重协商MTU大小时/* For ecoc reconfig is handled below in l2c_ble. In case of success* let us notify upper layer about the reconfig*/LOG_INFO("Calling LeReconfigCompleted_Cb(), CID: 0x%04x",p_ccb->local_cid);// 调用上层注册的Reconfig完成回调,告诉上层“Peer发起了重配置,处理结果是成功的”。(*p_ccb->p_rcb->api.pL2CA_CreditBasedReconfigCompleted_Cb)(p_lcb->remote_bd_addr, p_ccb->local_cid, false, p_le_cfg);break;case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */// 对端发来配置请求// 调用底层函数解析对端发来的配置请求内容。cfg_result = l2cu_process_peer_cfg_req(p_ccb, p_cfg);if (cfg_result == L2CAP_PEER_CFG_OK) { // 对端配置没问题。LOG_INFO("Calling Config_Req_Cb(), CID: 0x%04x, C-bit %d",p_ccb->local_cid, (p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT));l2c_csm_send_config_rsp_ok(p_ccb); // 发送Config Response(OK)给对端。if (p_ccb->config_done & OB_CFG_DONE) { // 如果自己也已经完成对对方的配置(即 OB_CFG_DONE),检查是否能进入 OPEN 状态if (p_ccb->remote_config_rsp_result == L2CAP_CFG_OK) {l2c_csm_indicate_connection_open(p_ccb); // 如果对端返回OK,那就打开连接;} else { // 否则如果我是发起方,本地上报错误。if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) {(*p_ccb->p_rcb->api.pL2CA_Error_Cb)(p_ccb->local_cid,L2CAP_CFG_FAILED_NO_REASON);bluetooth::shim::CountCounterMetrics(android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONFIG_REQ_FAILURE,1);}}}} else if (cfg_result == L2CAP_PEER_CFG_DISCONNECT) { // 配置严重不兼容,需要断开/* Disconnect if channels are incompatible */LOG_INFO("incompatible configurations disconnect");l2cu_disconnect_chnl(p_ccb); //断开连接。} else /* Return error to peer so it can renegotiate if possible */{ // 配置部分不兼容,需要协商:LOG_INFO("incompatible configurations trying reconfig");l2cu_send_peer_config_rsp(p_ccb, p_cfg); // 发送配置响应,表示部分参数不接受,等对端重新发新的配置。}break;case L2CEVT_L2CAP_CREDIT_BASED_RECONFIG_RSP:// 收到 BLE Credit-Based 连接重新配置的响应。// 更新状态:配置完成,连接状态改为 OPEN,取消超时定时器。p_ccb->config_done |= OB_CFG_DONE;p_ccb->config_done |= RECONFIG_FLAG;p_ccb->chnl_state = CST_OPEN;alarm_cancel(p_ccb->l2c_ccb_timer);LOG_INFO("Calling Config_Rsp_Cb(), CID: 0x%04x", p_ccb->local_cid);// 通知上层,Credit-Based重配置成功完成(`true` 表示成功)。p_ccb->p_rcb->api.pL2CA_CreditBasedReconfigCompleted_Cb(p_lcb->remote_bd_addr, p_ccb->local_cid, true, p_le_cfg);break;case L2CEVT_L2CAP_CONFIG_RSP: /* Peer config response */// 对端发来了配置响应l2cu_process_peer_cfg_rsp(p_ccb, p_cfg); // 处理对端的配置响应,把对端配置更新到本地控制块 p_ccb->peer_cfg/* TBD: When config options grow beyong minimum MTU (48 bytes)* logic needs to be added to handle responses with* continuation bit set in flags field.* 1. Send additional config request out until C-bit is cleared in* response*/p_ccb->config_done |= OB_CFG_DONE; // 标记自己发出去的配置已经完成了,OB_CFG_DONE(OutBound Config Done)。if (p_ccb->config_done & IB_CFG_DONE) { // 如果对端的配置也完成了(InBound Config Done),说明双方配置都搞定了,可以考虑继续后续操作。/* Verify two sides are in compatible modes before continuing */if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) {// 检查自己和对端的FCR模式(Flow Control and Retransmission mode,比如 Basic/ERTM/Streaming)是否一致。 如果不一致,无法通信,需要断开l2cu_send_peer_disc_req(p_ccb); // 发送 Disconnect Request 给对方。LOG_WARN("Calling Disconnect_Ind_Cb(Incompatible CFG), CID: ""0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb); // 释放 CCB 资源,通知上层断开连接(false 表示无须上层回复)。(*disconnect_ind)(local_cid, false);break;}// 如果配置兼容:p_ccb->config_done |= RECONFIG_FLAG; // 标记为已经重新配置(即配置完成后还可以后续改配)。p_ccb->chnl_state = CST_OPEN; // 把信道状态切换为 CST_OPEN,进入数据传输阶段。l2c_link_adjust_chnl_allocation(); // 调整物理链路资源分配。alarm_cancel(p_ccb->l2c_ccb_timer); // 取消配置超时定时器/* If using eRTM and waiting for an ACK, restart the ACK timer */if (p_ccb->fcrb.wait_ack) l2c_fcr_start_timer(p_ccb); // 如果在用增强模式(eRTM)且在等ACK包,则重启ACK定时器/*** check p_ccb->our_cfg.fcr.mon_tout and*p_ccb->our_cfg.fcr.rtrans_tout** we may set them to zero when sending config request during*renegotiation*/if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) &&((p_ccb->our_cfg.fcr.mon_tout == 0) ||(p_ccb->our_cfg.fcr.rtrans_tout))) {// 如果使用 eRTM 且 monitor timeout / retransmission timeout 不合理(=0),调整一下参数。l2c_fcr_adj_monitor_retran_timeout(p_ccb);}/* See if we can forward anything on the hold queue */if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) {// 检查有没有挂起的待发数据,有的话现在可以发了!l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL);}}LOG_INFO("Calling Config_Rsp_Cb(), CID: 0x%04x", p_ccb->local_cid); // 打日志,表明调用了配置完成回调p_ccb->remote_config_rsp_result = p_cfg->result; // 记录对端回复的配置结果(比如成功/失败码)。if (p_ccb->config_done & IB_CFG_DONE) {l2c_csm_indicate_connection_open(p_ccb); // 如果对端配置也完成,通知上层连接正式建立}break;case L2CEVT_L2CAP_CONFIG_RSP_NEG: /* Peer config error rsp */// 对端配置失败回复/* Disable the Timer */alarm_cancel(p_ccb->l2c_ccb_timer); // 停掉配置超时定时器。/* If failure was channel mode try to renegotiate */if (!l2c_fcr_renegotiate_chan(p_ccb, p_cfg)) { // 如果不能重新协商频道(比如因为模式不兼容),执行失败处理LOG_INFO("Calling Config_Rsp_Cb(), CID: 0x%04x, Failure: %d",p_ccb->local_cid, p_cfg->result);if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) {// 如果是本地发起连接的角色,通知上层出错(*p_ccb->p_rcb->api.pL2CA_Error_Cb)(p_ccb->local_cid,L2CAP_CFG_FAILED_NO_REASON);bluetooth::shim::CountCounterMetrics(android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONFIG_RSP_NEG,1);}}break;// 对端主动断开请求case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 设置断开流程的超时器p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; // 切换到等待上层应用回复 L2CA_DisconnectRsp 的状态。LOG_INFO("Calling Disconnect_Ind_Cb(), CID: 0x%04x Conf Needed",p_ccb->local_cid);(*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, true); // 通知上层:对端请求断开连接(需要应用层回复)。l2c_csm_send_disconnect_rsp(p_ccb); // 同时主动发出 Disconnect Response。break;case L2CEVT_L2CA_CREDIT_BASED_RECONFIG_REQ:// 本地发起credit-based重新配置请求l2cu_send_credit_based_reconfig_req(p_ccb, (tL2CAP_LE_CFG_INFO*)p_data); // 发出 Credit Based Channel 的 Reconfiguration Requestalarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 设置 Reconfig 超时定时器。break;case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */// 本地应用层发来配置请求l2cu_process_our_cfg_req(p_ccb, p_cfg); // 处理自己配置请求内容,发给对端。l2cu_send_peer_config_req(p_ccb, p_cfg);alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 设置配置超时定时器。break;case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config rsp */// 本地应用层收到对端的配置响应l2cu_process_our_cfg_rsp(p_ccb, p_cfg); // 处理对端返回的响应并标记 IB_CFG_DONE。p_ccb->config_done |= IB_CFG_DONE;// 后续逻辑跟前面的 CONFIG_RSP 类似,不再赘述。if (p_ccb->config_done & OB_CFG_DONE) {/* Verify two sides are in compatible modes before continuing */if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) {l2cu_send_peer_disc_req(p_ccb);LOG_WARN("Calling Disconnect_Ind_Cb(Incompatible CFG), CID: ""0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb);(*disconnect_ind)(local_cid, false);break;}p_ccb->config_done |= RECONFIG_FLAG;p_ccb->chnl_state = CST_OPEN;l2c_link_adjust_chnl_allocation();alarm_cancel(p_ccb->l2c_ccb_timer);}l2cu_send_peer_config_rsp(p_ccb, p_cfg);/* If using eRTM and waiting for an ACK, restart the ACK timer */if (p_ccb->fcrb.wait_ack) l2c_fcr_start_timer(p_ccb);/* See if we can forward anything on the hold queue */if ((p_ccb->chnl_state == CST_OPEN) &&(!fixed_queue_is_empty(p_ccb->xmit_hold_q))) {l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL);}break;case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */// 上层要求断开连接// 发送 Disconnect Request,切换到等待响应状态。l2cu_send_peer_disc_req(p_ccb);p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP;alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_DISCONNECT_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 设置断开超时器。break;case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */// 收到对端的数据包LOG_INFO("Calling DataInd_Cb(), CID: 0x%04x", p_ccb->local_cid);// 判断是否是固定信道:if (p_ccb->local_cid >= L2CAP_FIRST_FIXED_CHNL &&p_ccb->local_cid <= L2CAP_LAST_FIXED_CHNL) {if (p_ccb->local_cid < L2CAP_BASE_APPL_CID) {// 如果是且注册了固定信道的回调,则直接调用。if (l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb != nullptr) {p_ccb->metrics.rx(static_cast<BT_HDR*>(p_data)->len);(*l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)(p_ccb->local_cid,p_ccb->p_lcb->remote_bd_addr,(BT_HDR*)p_data);} else {if (p_data != nullptr) osi_free_and_reset(&p_data);}break;}}// 否则调用普通动态信道的回调 pL2CA_DataInd_Cb。if (p_data) p_ccb->metrics.rx(static_cast<BT_HDR*>(p_data)->len);(*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR*)p_data);break;case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */// 本地应用层发数据if (p_ccb->config_done & OB_CFG_DONE)// 如果自己发出去的配置完成了,排入发送队列,否则丢弃数据l2c_enqueue_peer_data(p_ccb, (BT_HDR*)p_data);elseosi_free(p_data);break;case L2CEVT_TIMEOUT: // 配置或连接过程超时if (p_ccb->ecoc) {// 如果是 Credit Based ECOC 连接,且处于 Reconfig 中,遍历所有 CCB,强制断开。for (temp_p_ccb = p_lcb->ccb_queue.p_first_ccb; temp_p_ccb;temp_p_ccb = temp_p_ccb->p_next_ccb) {if ((temp_p_ccb->in_use) && (temp_p_ccb->reconfig_started)) {(*temp_p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(temp_p_ccb->local_cid, false);l2cu_release_ccb(temp_p_ccb);}}acl_disconnect_from_handle(p_ccb->p_lcb->Handle(), HCI_ERR_CONN_CAUSE_LOCAL_HOST,"stack::l2cap::l2c_csm::l2c_csm_config timeout");return;}// 否则发送 Disconnect Request 断开连接。l2cu_send_peer_disc_req(p_ccb);LOG_INFO("Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb);(*disconnect_ind)(local_cid, false);break;default:LOG_ERROR("Handling unexpected event:%s", l2c_csm_get_event_name(event));}LOG_INFO("Exit chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);
}
目前 这个阶段,我们只需要关注:
case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */// 本地应用层发来配置请求l2cu_process_our_cfg_req(p_ccb, p_cfg); // 处理自己配置请求内容,发给对端。l2cu_send_peer_config_req(p_ccb, p_cfg);alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb); // 设置配置超时定时器。break;
- l2cu_send_peer_config_req(p_ccb, p_cfg); 向对端发送 配置请求。
- 此时并没有切状态
1083 2025-04-24 15:56:22.505308 22:22:96:de:b1:39 (leo 8295 chan) vivoMobi_91:b0:62 (cbx) L2CAP 21 Sent Configure Request (DCID: 0x0041)Frame 1083: 21 bytes on wire (168 bits), 21 bytes captured (168 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 12CID: L2CAP Signaling Channel (0x0001)Command: Configure RequestCommand Code: Configure Request (0x04)Command Identifier: 0x0eCommand Length: 8Destination CID: Dynamically Allocated Channel (0x0041)0000 0000 0000 000. = Reserved: 0x0000.... .... .... ...0 = Continuation Flag: FalseOption: MTU04-24 15:56:22.505097 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_CONFIG [5], event=UPPER_LAYER_CONFIG_REQ [24]04-24 15:56:22.505104 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:975 l2c_csm_config: LCID: 0x0046 st: CONFIG evt: UPPER_LAYER_CONFIG_REQ04-24 15:56:22.505267 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:1235 l2c_csm_config: Exit chnl_state=CST_CONFIG [5], event=UPPER_LAYER_CONFIG_REQ [24]
4. 接收到手机的配置请求
1089 2025-04-24 15:56:22.598410 vivoMobi_91:b0:62 (cbx) 22:22:96:de:b1:39 (leo 8295 chan) L2CAP 21 Rcvd Configure Request (DCID: 0x0046)Frame 1089: 21 bytes on wire (168 bits), 21 bytes captured (168 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 12CID: L2CAP Signaling Channel (0x0001)Command: Configure RequestCommand Code: Configure Request (0x04)Command Identifier: 0x12Command Length: 8Destination CID: Dynamically Allocated Channel (0x0046)0000 0000 0000 000. = Reserved: 0x0000.... .... .... ...0 = Continuation Flag: FalseOption: MTU04-24 15:56:22.598728 6130 6190 I bt_l2c_main: packages/modules/Bluetooth/system/stack/l2cap/l2c_main.cc:310 process_l2cap_cmd: cmd_code: 4, id:18, cmd_len:804-24 15:56:22.598769 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_CONFIG [5], event=PEER_CONFIG_REQ [14]04-24 15:56:22.598780 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:975 l2c_csm_config: LCID: 0x0046 st: CONFIG evt: PEER_CONFIG_REQ04-24 15:56:22.598793 6130 6190 I bt_l2cap: packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: l2c_fcr_process_peer_cfg_req() CFG fcr_present:0 fcr.mode:0 CCB FCR mode:0 preferred: 004-24 15:56:22.598803 6130 6190 I bt_l2cap: packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: l2cu_adjust_out_mps use 0 Based on peer_cfg.fcr.mps: 0 packet_size: 101104-24 15:56:22.598812 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:999 l2c_csm_config: Calling Config_Req_Cb(), CID: 0x0046, C-bit 0
- 此时我们收到了手机 对我们发起的 配置请求, 我们看一下该如何处理。
#define L2CAP_CMD_CONFIG_REQ 0x04// system/stack/l2cap/l2c_main.cc process_l2cap_cmd 中对于 cmd_code: 4 的处理case L2CAP_CMD_CONFIG_REQ: {uint8_t* p_cfg_end = p + cmd_len; // 记录配置请求的结尾位置,用于控制循环边界。// 标记是否发现不支持或格式错误的配置项,并统计被拒绝的长度(用于稍后构造拒绝响应)。bool cfg_rej = false;uint16_t cfg_rej_len = 0;// 每个配置请求头部需要 4 字节(LCID + FLAGS),数据不够就返回。uint16_t lcid;if (p + 4 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CMD_CONFIG_REQ");return;}STREAM_TO_UINT16(lcid, p); // lcid:本地通道号,表示配置哪个通道。STREAM_TO_UINT16(cfg_info.flags, p); // 如 Continuation 标志(如果选项太长需要分段发送)。uint8_t* p_cfg_start = p; // 记录配置选项的起始位置,用于可能的拒绝响应。// 清除所有配置标志位(标记哪些选项被包含)。cfg_info.flush_to_present = cfg_info.mtu_present =cfg_info.qos_present = cfg_info.fcr_present = cfg_info.fcs_present =false;// 开始解析每个配置选项while (p < p_cfg_end) {// 进入配置选项循环,直到处理完所有内容。// 每个选项结构:code (1B) + len (1B) + data (len B)uint8_t cfg_code, cfg_len;if (p + 2 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CMD_CONFIG_REQ sub_event");return;}STREAM_TO_UINT8(cfg_code, p);STREAM_TO_UINT8(cfg_len, p);// 处理每种配置类型switch (cfg_code & 0x7F) {case L2CAP_CFG_TYPE_MTU: // MTU 设置cfg_info.mtu_present = true;if (cfg_len != 2) {return;}if (p + cfg_len > p_next_cmd) {return;}STREAM_TO_UINT16(cfg_info.mtu, p);break;// Flush Timeout 设置case L2CAP_CFG_TYPE_FLUSH_TOUT:cfg_info.flush_to_present = true;if (cfg_len != 2) {return;}if (p + cfg_len > p_next_cmd) {return;}STREAM_TO_UINT16(cfg_info.flush_to, p);break;// QoS 设置(14 字节)case L2CAP_CFG_TYPE_QOS:cfg_info.qos_present = true;if (cfg_len != 2 + 5 * 4) {return;}if (p + cfg_len > p_next_cmd) {return;}STREAM_TO_UINT8(cfg_info.qos.qos_flags, p);STREAM_TO_UINT8(cfg_info.qos.service_type, p);STREAM_TO_UINT32(cfg_info.qos.token_rate, p);STREAM_TO_UINT32(cfg_info.qos.token_bucket_size, p);STREAM_TO_UINT32(cfg_info.qos.peak_bandwidth, p);STREAM_TO_UINT32(cfg_info.qos.latency, p);STREAM_TO_UINT32(cfg_info.qos.delay_variation, p);break;// FCR(流控制与重传)设置(9 字节)case L2CAP_CFG_TYPE_FCR:cfg_info.fcr_present = true;if (cfg_len != 3 + 3 * 2) {return;}if (p + cfg_len > p_next_cmd) {return;}STREAM_TO_UINT8(cfg_info.fcr.mode, p);STREAM_TO_UINT8(cfg_info.fcr.tx_win_sz, p);STREAM_TO_UINT8(cfg_info.fcr.max_transmit, p);STREAM_TO_UINT16(cfg_info.fcr.rtrans_tout, p);STREAM_TO_UINT16(cfg_info.fcr.mon_tout, p);STREAM_TO_UINT16(cfg_info.fcr.mps, p);break;// FCS 设置(Frame Check Sequence)case L2CAP_CFG_TYPE_FCS:cfg_info.fcs_present = true;if (cfg_len != 1) {return;}if (p + cfg_len > p_next_cmd) {return;}STREAM_TO_UINT8(cfg_info.fcs, p);break;// 扩展流控设置(EXT FLOW)case L2CAP_CFG_TYPE_EXT_FLOW:cfg_info.ext_flow_spec_present = true;if (cfg_len != 2 + 2 + 3 * 4) {return;}if (p + cfg_len > p_next_cmd) {return;}STREAM_TO_UINT8(cfg_info.ext_flow_spec.id, p);STREAM_TO_UINT8(cfg_info.ext_flow_spec.stype, p);STREAM_TO_UINT16(cfg_info.ext_flow_spec.max_sdu_size, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.sdu_inter_time, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.access_latency, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.flush_timeout, p);break;// 处理未知或格式错误的选项default:/* sanity check option length */if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= cmd_len) {if (p + cfg_len > p_next_cmd) return;p += cfg_len;// 注:cfg_code 第 7 位为 1 表示此选项“不可拒绝”if ((cfg_code & 0x80) == 0) { // // “可拒绝”位没设置cfg_rej_len += cfg_len + L2CAP_CFG_OPTION_OVERHEAD;cfg_rej = true;}}/* bad length; force loop exit */else {p = p_cfg_end;cfg_rej = true;}break;}}// 查找此配置请求对应的通道 CCB.tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);if (p_ccb) {// 如果 CCB 存在:p_ccb->remote_id = id;if (cfg_rej) {// 若发现不合法配置项,则发送 Config Reject。l2cu_send_peer_config_rej(p_ccb, p_cfg_start, (uint16_t)(cmd_len - L2CAP_CONFIG_REQ_LEN),cfg_rej_len);} else {// 否则将 cfg_info 交给状态机处理配置请求。l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_REQ, &cfg_info);}} else {// 如果 CCB 不存在:// 通道无效,发送 Command Reject(CID 无效)响应。/* updated spec says send command reject on invalid cid */l2cu_send_peer_cmd_reject(p_lcb, L2CAP_CMD_REJ_INVALID_CID, id, 0, 0);}break;}
- 将 cfg_info 交给状态机处理配置请求。
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_REQ, &cfg_info);
这里发送了 L2CEVT_L2CAP_CONFIG_REQ 消息给状态机
L2CEVT_L2CAP_CONFIG_REQ = 14, /* request */
此时 p_ccb->chnl_state 依然是 CST_CONFIG 状态,
void l2c_csm_execute(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {if (p_ccb == nullptr) {LOG_WARN("CCB is null for event (%d)", event);return;}LOG_INFO("Entry chnl_state=%s [%d], event=%s [%d]",channel_state_text(p_ccb->chnl_state).c_str(), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);switch (p_ccb->chnl_state) {case CST_CONFIG:l2c_csm_config(p_ccb, event, p_data);break;break;}}
// 下面是 l2c_csm_config 函数中对于 L2CEVT_L2CAP_CONFIG_REQ 的处理case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */// 对端发来配置请求// 调用底层函数解析对端发来的配置请求内容。cfg_result = l2cu_process_peer_cfg_req(p_ccb, p_cfg);if (cfg_result == L2CAP_PEER_CFG_OK) { // 对端配置没问题。LOG_INFO("Calling Config_Req_Cb(), CID: 0x%04x, C-bit %d",p_ccb->local_cid, (p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT));l2c_csm_send_config_rsp_ok(p_ccb); // 发送Config Response(OK)给对端。if (p_ccb->config_done & OB_CFG_DONE) { // 如果自己也已经完成对对方的配置(即 OB_CFG_DONE),检查是否能进入 OPEN 状态if (p_ccb->remote_config_rsp_result == L2CAP_CFG_OK) {l2c_csm_indicate_connection_open(p_ccb); // 如果对端返回OK,那就打开连接;} else { // 否则如果我是发起方,本地上报错误。if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) {(*p_ccb->p_rcb->api.pL2CA_Error_Cb)(p_ccb->local_cid,L2CAP_CFG_FAILED_NO_REASON);bluetooth::shim::CountCounterMetrics(android::bluetooth::CodePathCounterKeyEnum::L2CAP_CONFIG_REQ_FAILURE,1);}}}} else if (cfg_result == L2CAP_PEER_CFG_DISCONNECT) { // 配置严重不兼容,需要断开/* Disconnect if channels are incompatible */LOG_INFO("incompatible configurations disconnect");l2cu_disconnect_chnl(p_ccb); //断开连接。} else /* Return error to peer so it can renegotiate if possible */{ // 配置部分不兼容,需要协商:LOG_INFO("incompatible configurations trying reconfig");l2cu_send_peer_config_rsp(p_ccb, p_cfg); // 发送配置响应,表示部分参数不接受,等对端重新发新的配置。}break;
- l2c_csm_send_config_rsp_ok : 向手机 侧发送了 配置应答, 状态没有变化还是 CST_CONFIG
5. 向手机发送配置应答
1090 2025-04-24 15:56:22.598958 22:22:96:de:b1:39 (leo 8295 chan) vivoMobi_91:b0:62 (cbx) L2CAP 19 Sent Configure Response - Success (SCID: 0x0041)Frame 1090: 19 bytes on wire (152 bits), 19 bytes captured (152 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 10CID: L2CAP Signaling Channel (0x0001)Command: Configure ResponseCommand Code: Configure Response (0x05)Command Identifier: 0x12Command Length: 6Source CID: Dynamically Allocated Channel (0x0041)0000 0000 0000 000. = Reserved: 0x0000.... .... .... ...0 = Continuation Flag: FalseResult: Success (0x0000)04-24 15:56:22.598819 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_CONFIG [5], event=UPPER_LAYER_CONFIG_RSP [25]04-24 15:56:22.598826 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:975 l2c_csm_config: LCID: 0x0046 st: CONFIG evt: UPPER_LAYER_CONFIG_RSP
- 调用 l2c_csm_send_config_rsp_ok 来处理 向手机发送配置应答的逻辑。
static void l2c_csm_send_config_rsp_ok(tL2C_CCB* p_ccb) {tL2CAP_CFG_INFO config{};config.result = L2CAP_CFG_OK;l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONFIG_RSP, &config);
}
- 发现这里有触发了 L2CEVT_L2CA_CONFIG_RSP 事件
// 此时状态还是 CST_CONFIG, 所以 l2c_csm_execute 继续调用 l2c_csm_config 函数
// 下面是 l2c_csm_config 函数中对于 L2CEVT_L2CA_CONFIG_RSP 的处理L2CEVT_L2CA_CONFIG_RSP = 25, /* config response */case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config rsp */// 本地应用层收到对端的配置响应l2cu_process_our_cfg_rsp(p_ccb, p_cfg); // 处理对端返回的响应并标记 IB_CFG_DONE。p_ccb->config_done |= IB_CFG_DONE;// 后续逻辑跟前面的 CONFIG_RSP 类似,不再赘述。if (p_ccb->config_done & OB_CFG_DONE) {/* Verify two sides are in compatible modes before continuing */if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) {l2cu_send_peer_disc_req(p_ccb);LOG_WARN("Calling Disconnect_Ind_Cb(Incompatible CFG), CID: ""0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb);(*disconnect_ind)(local_cid, false);break;}p_ccb->config_done |= RECONFIG_FLAG;p_ccb->chnl_state = CST_OPEN;l2c_link_adjust_chnl_allocation();alarm_cancel(p_ccb->l2c_ccb_timer);}l2cu_send_peer_config_rsp(p_ccb, p_cfg); // 这里是向手机 发送配置响应的地方。/* If using eRTM and waiting for an ACK, restart the ACK timer */if (p_ccb->fcrb.wait_ack) l2c_fcr_start_timer(p_ccb);/* See if we can forward anything on the hold queue */if ((p_ccb->chnl_state == CST_OPEN) &&(!fixed_queue_is_empty(p_ccb->xmit_hold_q))) {l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL);}break;
- l2cu_send_peer_config_rsp(p_ccb, p_cfg): 这里是向手机 发送配置响应的地方。
6. 接收到手机的配置应答
1091 2025-04-24 15:56:22.599401 vivoMobi_91:b0:62 (cbx) 22:22:96:de:b1:39 (leo 8295 chan) L2CAP 19 Rcvd Configure Response - Success (SCID: 0x0046)Frame 1091: 19 bytes on wire (152 bits), 19 bytes captured (152 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP ProtocolLength: 10CID: L2CAP Signaling Channel (0x0001)Command: Configure ResponseCommand Code: Configure Response (0x05)Command Identifier: 0x0eCommand Length: 6Source CID: Dynamically Allocated Channel (0x0046)0000 0000 0000 000. = Reserved: 0x0000.... .... .... ...0 = Continuation Flag: FalseResult: Success (0x0000)04-24 15:56:22.599604 6130 6190 I bt_l2c_main: packages/modules/Bluetooth/system/stack/l2cap/l2c_main.cc:310 process_l2cap_cmd: cmd_code: 5, id:14, cmd_len:604-24 15:56:22.599636 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:129 l2c_csm_execute: Entry chnl_state=CST_CONFIG [5], event=PEER_CONFIG_RSP [15]04-24 15:56:22.599646 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:975 l2c_csm_config: LCID: 0x0046 st: CONFIG evt: PEER_CONFIG_RSP04-24 15:56:22.599658 6130 6190 I l2c_link: packages/modules/Bluetooth/system/stack/l2cap/l2c_link.cc:739 l2c_link_adjust_chnl_allocation: CID:0x0042 FCR Mode:0 Priority:2 TxDataRate:1 RxDataRate:1 Quota:20004-24 15:56:22.599667 6130 6190 I l2c_link: packages/modules/Bluetooth/system/stack/l2cap/l2c_link.cc:739 l2c_link_adjust_chnl_allocation: CID:0x0046 FCR Mode:0 Priority:2 TxDataRate:1 RxDataRate:1 Quota:20004-24 15:56:22.599681 6130 6190 I l2c_csm : packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc:1088 l2c_csm_config: Calling Config_Rsp_Cb(), CID: 0x0046
- 在 2025-04-24 15:56:22.599401 收到了 手机 对车机的配置应答。 我们来看一下是如何响应处理的
- process_l2cap_cmd: cmd_code: 5
#define L2CAP_CMD_CONFIG_RSP 0x05static void process_l2cap_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) {case L2CAP_CMD_CONFIG_RSP: {/*计算当前命令数据的结束地址,用于限制 while (p < p_cfg_end) 的解析范围。p 是当前命令起始指针,cmd_len 是该命令的数据长度。*/uint8_t* p_cfg_end = p + cmd_len; uint16_t lcid; // 本地通道 ID,用于查找连接控制块(CCB)。// 确保当前命令数据至少有 6 字节可读:LCID (2) + Flags (2) + Result (2)。if (p + 6 > p_next_cmd) {// 如果不满足,表示数据不完整,记录警告日志并返回。LOG_WARN("Not enough data for L2CAP_CMD_CONFIG_RSP");return;}/*从数据流中按顺序解析:lcid:本地通道 IDcfg_info.flags:配置标志(是否继续配置等)cfg_info.result:配置结果,例如 L2CAP_CFG_OK*/STREAM_TO_UINT16(lcid, p);STREAM_TO_UINT16(cfg_info.flags, p);STREAM_TO_UINT16(cfg_info.result, p);/*清空所有配置字段的标志位,准备重新填充配置项。cfg_info 是一个结构体,保存了本次解析得到的配置信息。*/cfg_info.flush_to_present = cfg_info.mtu_present =cfg_info.qos_present = cfg_info.fcr_present = cfg_info.fcs_present =false;// 开始循环解析各个配置选项:while (p < p_cfg_end) {// 遍历整个配置项字段,直到到达当前命令末尾。uint8_t cfg_code, cfg_len;if (p + 2 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CMD_CONFIG_RSP sub_event");return;}// 获取当前配置项的类型(如 MTU、FCR 等)和长度。STREAM_TO_UINT8(cfg_code, p);STREAM_TO_UINT8(cfg_len, p);switch (cfg_code & 0x7F) { // cfg_code 高位第 7 位用于“是否是可选项”,低 7 位才是实际类型。case L2CAP_CFG_TYPE_MTU: // MTU 大小cfg_info.mtu_present = true; // 标记为已解析。if (p + 2 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CFG_TYPE_MTU");return;}STREAM_TO_UINT16(cfg_info.mtu, p); // 读取 2 字节 MTU 数值break;case L2CAP_CFG_TYPE_FLUSH_TOUT: // Flush Timeoutcfg_info.flush_to_present = true;if (p + 2 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CFG_TYPE_FLUSH_TOUT");return;}STREAM_TO_UINT16(cfg_info.flush_to, p);break;case L2CAP_CFG_TYPE_QOS:// 服务质量配置cfg_info.qos_present = true;if (p + 2 + 5 * 4 > p_next_cmd) { // 一共 22 字节:1 + 1 + 5 * 4LOG_WARN("Not enough data for L2CAP_CFG_TYPE_QOS");return;}// 包括 token rate、带宽、延迟等。STREAM_TO_UINT8(cfg_info.qos.qos_flags, p);STREAM_TO_UINT8(cfg_info.qos.service_type, p);STREAM_TO_UINT32(cfg_info.qos.token_rate, p);STREAM_TO_UINT32(cfg_info.qos.token_bucket_size, p);STREAM_TO_UINT32(cfg_info.qos.peak_bandwidth, p);STREAM_TO_UINT32(cfg_info.qos.latency, p);STREAM_TO_UINT32(cfg_info.qos.delay_variation, p);break;case L2CAP_CFG_TYPE_FCR: // 流控/重传配置cfg_info.fcr_present = true;if (p + 3 + 3 * 2 > p_next_cmd) { // 一共 9 字节:3 + 3 * 2LOG_WARN("Not enough data for L2CAP_CFG_TYPE_FCR");return;}// 包括模式、窗口大小、超时值等。STREAM_TO_UINT8(cfg_info.fcr.mode, p);STREAM_TO_UINT8(cfg_info.fcr.tx_win_sz, p);STREAM_TO_UINT8(cfg_info.fcr.max_transmit, p);STREAM_TO_UINT16(cfg_info.fcr.rtrans_tout, p);STREAM_TO_UINT16(cfg_info.fcr.mon_tout, p);STREAM_TO_UINT16(cfg_info.fcr.mps, p);break;case L2CAP_CFG_TYPE_FCS: // 帧校验cfg_info.fcs_present = true;if (p + 1 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CFG_TYPE_FCS");return;}// 读取 1 字节的 fcs 校验类型。STREAM_TO_UINT8(cfg_info.fcs, p);break;case L2CAP_CFG_TYPE_EXT_FLOW:// 扩展流控制cfg_info.ext_flow_spec_present = true;if (p + 2 + 2 + 3 * 4 > p_next_cmd) {LOG_WARN("Not enough data for L2CAP_CFG_TYPE_EXT_FLOW");return;}// 读取 ID、服务类型、SDU 尺寸、延迟等。STREAM_TO_UINT8(cfg_info.ext_flow_spec.id, p);STREAM_TO_UINT8(cfg_info.ext_flow_spec.stype, p);STREAM_TO_UINT16(cfg_info.ext_flow_spec.max_sdu_size, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.sdu_inter_time, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.access_latency, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.flush_timeout, p);break;}}// 根据 lcid 查找对应的连接控制块(CCB)。tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);if (p_ccb) {if (p_ccb->local_id != id) {// 检查这条响应命令的标识符 id 是否与原请求一致,不一致可能是延迟或错误响应。LOG_WARN("cfg rsp - bad ID. Exp: %d Got: %d", p_ccb->local_id, id);break;}// 成功响应与否:if (cfg_info.result == L2CAP_CFG_OK) { // 表示配置接受// 使用状态机将配置结果作为事件投递到对应通道的状态机中处理。l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_RSP, &cfg_info);} else {// 否则认为配置失败,走 L2CEVT_L2CAP_CONFIG_RSP_NEGl2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_RSP_NEG, &cfg_info);}} else {// 找不到则丢弃// 输出日志说明:收到一个对未知通道的配置响应,可能是协议异常或状态不同步。LOG_WARN("Rcvd cfg rsp for unknown CID: 0x%04x", lcid);}break;}}
- l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_RSP, &cfg_info);
- 最终向 状态机 触发了 L2CEVT_L2CAP_CONFIG_RSP 消息
- L2CEVT_L2CAP_CONFIG_RSP = 15, /* response */
当前状态机还是 处于 CST_CONFIG
// 此时状态还是 CST_CONFIG, 所以 l2c_csm_execute 继续调用 l2c_csm_config 函数
// 下面是 l2c_csm_config 函数中对于 L2CEVT_L2CAP_CONFIG_RSP 的处理L2CEVT_L2CAP_CONFIG_RSP = 15, /* response */case L2CEVT_L2CAP_CONFIG_RSP: /* Peer config response */// 对端发来了配置响应l2cu_process_peer_cfg_rsp(p_ccb, p_cfg); // 处理对端的配置响应,把对端配置更新到本地控制块 p_ccb->peer_cfg/* TBD: When config options grow beyong minimum MTU (48 bytes)* logic needs to be added to handle responses with* continuation bit set in flags field.* 1. Send additional config request out until C-bit is cleared in* response*/p_ccb->config_done |= OB_CFG_DONE; // 标记自己发出去的配置已经完成了,OB_CFG_DONE(OutBound Config Done)。if (p_ccb->config_done & IB_CFG_DONE) { // 如果对端的配置也完成了(InBound Config Done),说明双方配置都搞定了,可以考虑继续后续操作。/* Verify two sides are in compatible modes before continuing */if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) {// 检查自己和对端的FCR模式(Flow Control and Retransmission mode,比如 Basic/ERTM/Streaming)是否一致。 如果不一致,无法通信,需要断开l2cu_send_peer_disc_req(p_ccb); // 发送 Disconnect Request 给对方。LOG_WARN("Calling Disconnect_Ind_Cb(Incompatible CFG), CID: ""0x%04x No Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb); // 释放 CCB 资源,通知上层断开连接(false 表示无须上层回复)。(*disconnect_ind)(local_cid, false);break;}// 如果配置兼容:p_ccb->config_done |= RECONFIG_FLAG; // 标记为已经重新配置(即配置完成后还可以后续改配)。p_ccb->chnl_state = CST_OPEN; // 把信道状态切换为 CST_OPEN,进入数据传输阶段。l2c_link_adjust_chnl_allocation(); // 调整物理链路资源分配。alarm_cancel(p_ccb->l2c_ccb_timer); // 取消配置超时定时器/* If using eRTM and waiting for an ACK, restart the ACK timer */if (p_ccb->fcrb.wait_ack) l2c_fcr_start_timer(p_ccb); // 如果在用增强模式(eRTM)且在等ACK包,则重启ACK定时器/*** check p_ccb->our_cfg.fcr.mon_tout and*p_ccb->our_cfg.fcr.rtrans_tout** we may set them to zero when sending config request during*renegotiation*/if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) &&((p_ccb->our_cfg.fcr.mon_tout == 0) ||(p_ccb->our_cfg.fcr.rtrans_tout))) {// 如果使用 eRTM 且 monitor timeout / retransmission timeout 不合理(=0),调整一下参数。l2c_fcr_adj_monitor_retran_timeout(p_ccb);}/* See if we can forward anything on the hold queue */if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) {// 检查有没有挂起的待发数据,有的话现在可以发了!l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL);}}LOG_INFO("Calling Config_Rsp_Cb(), CID: 0x%04x", p_ccb->local_cid); // 打日志,表明调用了配置完成回调p_ccb->remote_config_rsp_result = p_cfg->result; // 记录对端回复的配置结果(比如成功/失败码)。if (p_ccb->config_done & IB_CFG_DONE) {l2c_csm_indicate_connection_open(p_ccb); // 如果对端配置也完成,通知上层连接正式建立}break;
- 手机发起的配置请求, 我们已经响应了, 所以 p_ccb->config_done & IB_CFG_DONE 条件成立
- p_ccb->chnl_state = CST_OPEN;
- 此时会调用 l2c_csm_indicate_connection_open(p_ccb)
7. l2c_csm_indicate_connection_open
我们专门看一下这个函数的实现
// system/stack/l2cap/l2c_csm.cc
static void l2c_csm_indicate_connection_open(tL2C_CCB* p_ccb) {if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) { // 如果是发起方,就会回调如下(*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(p_ccb->local_cid, L2CAP_CONN_OK);} else {// 在这个场景中 车机并不是发起者,所以会调用如下(*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb)(p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, p_ccb->p_rcb->psm,p_ccb->remote_id);}if (p_ccb->chnl_state == CST_OPEN && !p_ccb->p_lcb->is_transport_ble()) {(*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_ccb->connection_initiator, &p_ccb->peer_cfg);}
}// system/stack/include/l2c_api.h
typedef struct {tL2CA_CONNECT_IND_CB* pL2CA_ConnectInd_Cb;tL2CA_CONNECT_CFM_CB* pL2CA_ConnectCfm_Cb;tL2CA_CONFIG_IND_CB* pL2CA_ConfigInd_Cb;tL2CA_CONFIG_CFM_CB* pL2CA_ConfigCfm_Cb;tL2CA_DISCONNECT_IND_CB* pL2CA_DisconnectInd_Cb;tL2CA_DISCONNECT_CFM_CB* pL2CA_DisconnectCfm_Cb;tL2CA_DATA_IND_CB* pL2CA_DataInd_Cb;tL2CA_CONGESTION_STATUS_CB* pL2CA_CongestionStatus_Cb;tL2CA_TX_COMPLETE_CB* pL2CA_TxComplete_Cb;tL2CA_ERROR_CB* pL2CA_Error_Cb;tL2CA_CREDIT_BASED_CONNECT_IND_CB* pL2CA_CreditBasedConnectInd_Cb;tL2CA_CREDIT_BASED_CONNECT_CFM_CB* pL2CA_CreditBasedConnectCfm_Cb;tL2CA_CREDIT_BASED_RECONFIG_COMPLETED_CB*pL2CA_CreditBasedReconfigCompleted_Cb;tL2CA_CREDIT_BASED_COLLISION_IND_CB* pL2CA_CreditBasedCollisionInd_Cb;
} tL2CAP_APPL_INFO;// system/stack/avdt/avdt_l2c.cc
const tL2CAP_APPL_INFO avdt_l2c_appl = {avdt_l2c_connect_ind_cback,avdt_l2c_connect_cfm_cback,avdt_l2c_config_ind_cback,avdt_l2c_config_cfm_cback,avdt_l2c_disconnect_ind_cback,NULL,avdt_l2c_data_ind_cback,avdt_l2c_congestion_ind_cback,NULL,avdt_on_l2cap_error,NULL,NULL,NULL,NULL};
-
这个函数会 触发 avdtp 对应的 pL2CA_ConnectInd_Cb 和 pL2CA_ConfigCfm_Cb 调用
-
所以此时会触发 avdt_l2c_connect_ind_cback 和 avdt_l2c_config_cfm_cback
04-24 15:56:22.599692 6130 6190 I bt_avp : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: trace_avdt avdt_l2c_connect_ind_cback 143 ->04-24 15:56:22.599759 6130 6190 I bt_avp : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: avdt_ccb_alloc_by_channel_index: allocated (index 0) peer=70:8f:47:91:b0:62 p_ccb=0x7a407afc2804-24 15:56:22.599782 6130 6190 I bt_avp : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: avdt_ad_tc_tbl_to_idx: 004-24 15:56:22.599793 6130 6190 I bt_avp : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: trace_avdt avdt_l2c_config_cfm_cback 306 ->04-24 15:56:22.599801 6130 6190 I bt_avp : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: trace_avdt avdt_l2c_config_ind_cback 339 ->
- 至于 avdtp 如何初始化, 不再本节的分析之内, 会在 avdtp 章节中,专门分享。
3. 小结
相关文章:
【android bluetooth 协议分析 06】【l2cap详解 10】【通过avdtp连接流程,感受l2cap通道的生命周期变化】
本篇我们通过分析一个具体的实例,来直观感受一下 l2cap 中通道的 状态变化。 1. 环境描述: 车机: a2dp sink手机: a2dp source场景: 手机主动 触发 连车机 声明一下: 分析的btsnoop 和 logcat 还有源码,…...
如何在idea中写spark程序。
要在IntelliJ IDEA中编写Spark程序,你可以按照以下步骤进行: 1. 安装和配置Java:确保你的计算机上已经安装了Java Development Kit (JDK),并且已配置好 JAVA_HOME 环境变量。 2. 安装IntelliJ IDEA:下载并安装Inte…...
Pytest-mark使用详解(跳过、标记、参数 化)
1.前言 在工作中我们经常使用pytest.mark.XXXX进行装饰器修饰,后面的XXX的不同,在pytest中有不同的作 用,其整体使用相对复杂,我们单独将其抽取出来做详细的讲解。 2.pytest.mark.skip()/skipif()跳过用例 import pytest #无条…...
[Android] GKD v1.10.3
[Android] GKD 链接:https://pan.xunlei.com/s/VOOwKvmwpLoLl7fLi6wJZKK-A1?pwd8mey# GKD(详情请戳 作者项目地址)是一款免费开源简洁多规则的自动跳过广告的软件,整体基于kotlin开发,免root即可使用。简而言之&am…...
C22-作业练习之最大公约数与最小公倍数
作业练习之最大公约数与最小公倍数 代码 #include <stdio.h> int main() {//变量初始化int m,n;int i,gcd,lcm;//数据录入printf("请输入两个整数:");scanf("%d %d",&m,&n);//求最大公约数int min(m<n)?m:n; //找m与n的最小值for(imi…...
信号完整性简介第一篇
本章将讲述信号完整性相关问题。首先提出什么是高速系统设计中的信号完整性,其次结合影响信号完整性的各种因素,深入讨论在高速系统设计过程中会碰到的几类信号完整性问题,并对每一类问题提出相应的预防措施和解决方案。 需要说明࿰…...
Qt开发环境的安装与问题的解决(2)
文章目录 1. Qt开发环境安装的说明2. 通过安装包进行安装3. 通过在线下载程序 解决问题下载 https....网路错误问题解决开始安装--第一部分开始安装--第二部分 4. 建议配置环境变量(非必须)配置环境变量的意义 简介:这篇文章主要分享Qt开发环…...
STM32 ADC模数转换器
一、ADC简介 ADC(Analog-Digital Converter)模拟-数字转换器ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁12位逐次逼近型ADC,1us转换时间 输入电压范围:0~3.3V&#x…...
World of Warcraft [CLASSIC] Hunter[Grandel] R12
World of Warcraft [CLASSIC] Hunter[Grandel] R12 R13,[Freeblue] 可惜当时没有截图,唉,没有纪念 --- 挂机脚本...
kalibr:相机模型
文章目录 📚简介Kalibr标定支持的相机模型及适用场景📌 针孔相机模型(Pinhole)🌐 全向相机模型(Omnidirectional)🔍 特殊模型💡 选型建议⚠️ 注意事项📚简介 Kalibr作为多传感器标定的重要工具,支持多种相机模型以适应不同光学特性的视觉传感器。其核心相机…...
【AI News | 20250428】每日AI进展
AI Repos 1、dyad Dyad 是一款免费开源的本地化 AI 应用开发工具,兼容 Windows 和 Mac 双平台。支持使用自有 API 密钥灵活调用主流 AI 模型(包括 Gemini、GPT-4.1、Claude 等),内置数据库与身份验证系统可快速构建完整应用。 …...
DNS区域的类型
在BIND9中,type 用于定义DNS区域的类型,不同的类型决定了BIND如何处理该区域的DNS数据。以下是主要的类型及其作用详解: 1. master(主区域) 作用: 表示该区域是权威DNS服务器的主副本,负责存储…...
HTTP vs HTTPS:传输协议的安全演进与核心差异
HTTP(HyperText Transfer Protocol)和HTTPS(HTTP Secure)作为两种最常用的协议,其安全性和实现方式直接关系到用户隐私和数据完整性。 目录 一、HTTP与HTTPS的基础概念 二、HTTP与HTTPS的核心差异 …...
Android平台Unity引擎的Mono JIT机制分析
一、分析背景 Unity引擎开发游戏采用了C#语言,Unity采用了Mono的Jit机制实现C#逻辑代码动态编译和执行,Mono属于开源的工程,可通过查看Mono源码了解其中的处理。本文针对Mono的Jit动态编译和执行的机制进行分析,更好的了解Unity引擎在Android平台所采用的处理方式。 二、M…...
【java】接口
一、定义 1.接口中所有方法都是抽象方法和公共方法(可以省略 abstract、public),不可以有正常的方法,所有变量都是全局静态常量 2.能继承接口的只能是接口 3.接口和类的关系通过实现(重写(实现)…...
Vuex(二) —— 用Vuex完成购物车案例
目录 需求需求分析 组件分析组件通信 开发 准备环境准备模块结构商品列表组件 展示商品列表添加购物车 我的购物车组件 购物车列表商品数量和统计功能删除购物车商品 购物车列表组件 购物车列表全选操作数字加减并统计小计删除功能统计总数量和总钱数处理金额小数的问题 本地存…...
数字孪生的浪潮:从虚拟镜像到现实世界的 IT 变革
文章目录 数字孪生的本质:物理与虚拟的实时镜像数字孪生的演进:从工业试验到全行业热潮核心技术:数字孪生的基石与工具链物联网(IoT):数据采集云计算与大数据:模型存储AI 与机器学习:…...
Web开发之三层架构
实例: 分层解耦 耦合:衡量软件中各个层/各个模块的依赖关联程度。 内聚:软件中各个功能模块内部的功能联系。 软件设计原则:高内聚低耦合 控制反转:Inversion 0f Control,简称IQC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为…...
社交电商和泛娱乐平台出海南美市场支付方式与策略
随着中国社交电商和泛娱乐平台加速全球化布局,南美市场凭借庞大的人口基数、快速增长的互联网渗透率和活跃的社交媒体使用率,成为出海企业的重要战略要地。然而,这片"新蓝海"的支付生态复杂多元,信用卡欺诈率高企,现金支付仍占主导,不同国家支付偏好差异显著。…...
Miniconda Windows10版本下载和安装
Miniconda Windows10版本下载和安装 步骤1:Miniconda3下载和安装 # 1、下载地址(Windows 64位) https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe #2、双击进行安装 # 安装注意事项: 1. 安装路径建议&a…...
编译原理:由浅入深从语法树到文法类型
文法与语言基础:从语法树到文法类型 文法(Grammar)和语言(Language)是计算机科学和语言学中解析和理解语言结构的核心概念。无论是编程语言的编译器设计,还是自然语言处理(NLP)中的…...
初识Python
哈哈哈,为了让初学者对python进一步了解,懒惰的博主特地去问了AI,如何更加形象的形容python这一么语言 🌟 Python 是什么? 想象一下,编程语言是一群性格各异的人: C语言:穿格子衫的…...
C++ —— 正向迭代器与反向迭代器
目录 1. 正向迭代器(Forward Iterator) 1.1 基本概念 1.2 核心特性 1.3 典型使用 1.4 重要特点 2. 反向迭代器(Reverse Iterator) 2.1 基本概念 2.2 核心特性 2.3 典型使用 2.4 重要特点 3. 正反迭代器对比 4. 正反迭代…...
FDA会议类型总结
1. 会议类型及目的 1.1 Type A会议 1.1.1 争议解决会议 用于解决药物研发过程中与FDA产生的争议,明确双方分歧点。 通过讨论达成共识,避免因争议影响研发进度。 1.1.2 临床保留讨论会议 针对临床试验中出现的问题进行讨论,决定是否保留临床试验。 综合评估临床试验的安全性…...
数据结构算法竞赛训练网站OJ(Online Judge)
都是个人使用过的算法训练OJ,存个档 洛谷 https://www.luogu.org/ 个人使用最多的,题目较全,每题都有题解博客,社区比较完善。 PTA https://pintia.cn/ 学习数据结构和练习天梯赛的时候使用的。 牛客 https://ac.nowcoder.co…...
快速搭建对象存储服务 - Minio,并解决临时地址暴露ip、短链接请求改变浏览器地址等问题
本文要解决的问题 基础的Minio下载安装、java操作方法、完整的工具类。 使用minio时需要注意的地方: 使用Minio的时候,生成资源的临时访问链接时,生成的地址IP是真实的IP和端口,不安全,怎么办? 生成的Min…...
2025年- H11-Lc118-53.最大子数组和(普通数组)---java版
1.题目描述 2.思路 用动态规划方法来解决【最大子数组和】(Maximum Subarray)问题。 pre(当前位置最大和)、 maxAns(全局最大和) 3.代码实现 class H53 {public int maxSubArray(int[] nums) {int curr…...
基于蓝牙Beacon人员导航方案
基于蓝牙Beacon人员导航方案 一、室内定位市场痛点与技术选择 大型商场(单层超2万㎡)和医院(科室超200个)的复杂空间中,传统GPS信号衰减超90%,用户平均寻路耗时10-15分钟,30%购物决策因“找店…...
mysql模糊多次OR查询某一个字段,针对这个字段进行查询分组
一. 需求 有一个mysql表t_test,有两个字段className和studentStr 其中studentStr会用来模糊查询 假如现在有6条数据 1.studentStr字段数据:“小明,小红,小同,小芳,小特,小兰” 2.studentStr字段数据:“小明,小红,小同” 3.studentStr字段数据:“小芳,小特,小兰” 4.stud…...
OpenGL进阶系列21 - OpenGL SuperBible - blendmatrix 例子学习
一:概述 颜色输出阶段是 OpenGL 渲染管线中最后一个阶段。它决定了片段在离开片段着色器之后,最终显示在用户屏幕上的颜色值。颜色输出阶段最重要的一个操作就是混合。本例子重点介绍下OpenGL中的混合操作。 对于每个通过片段测试(per-fragment tests)的片段,会执行混合操…...
阿里语音处理工具ClearerVoice-Studio项目上手指南
ClearerVoice-Studio:开源语音处理全能工具箱 🚀 核心功能速览 语音增强:消除环境噪声(支持16kHz/48kHz)语音分离:多人对话场景的说话人分离(8kHz/16kHz)超分辨率:16kHz→48kHz音质提升目标说话人提取:基于人脸/手势/EEG的多模态提取语音质量评估:9种客观评价指标A…...
31、简要描述Promise.all的用途
Promise.all 是 JavaScript 中用于处理多个异步操作的核心方法,其核心用途是并行聚合多个 Promise 的结果,并在所有操作成功时统一返回结果数组。以下是其关键特性与典型应用场景的简要描述: 核心功能 1、并行执行 接收一个 Promise 数组&…...
OpenVLA-OFT
TL;DR 2025 年斯坦福提出的 OpenVLA 工作的续作 OpenVLA-OFT,优化 VLA 能够有效适应新的机器人平台和任务,优化的技术主要有并行解码、动作块处理、连续动作、L1 回归和(可选的)FiLM 语言调节 Paper name Fine-Tuning Vision-La…...
2025“钉耙编程”中国大学生算法设计春季联赛(8)10031007
题目的意思很好理解找从最左边到最右边最短路(BFS) #include <bits/stdc.h> using namespace std; int a[510][510]; // 存储网格中每个位置是否有障碍(1表示有障碍,0表示无障碍) int v[510][510]; // 记录每…...
代码随想录算法训练营第六十一天 | floyd算法
Floyd 算法精讲 题目链接:97. 小明逛公园 文章讲解:代码随想录 思想:本题是多源最短路,即求多个起点到多个终点的多条最短路径。用Floyd 算法。 Floyd 算法对边的权值正负没有要求,都可以处理,Floyd算法…...
[三分钟]web自动化测试(三):selenium自动化测试常用函数(下)
文章目录 4.等待4.1 强制等待4.2 隐式等待4.3 显式等待 5.浏览器导航5.1 浏览器的前进、后退、刷新5.2 打开网站 6. 弹窗6.1 确认和取消6.2 输入信息 7. 文件上传 4.等待 如果页面渲染的速度赶不上代码执行的速度,可能会因为渲染过慢出现自动化误报的问题。 此时可…...
文档声明:HTML文档的基石
在前端开发的世界里,文档声明虽是一个看似不起眼的细节,却在网页的解析和渲染过程中扮演着至关重要的角色。今天,就让我们深入探讨文档声明的奥秘,揭开它背后的原理和重要性。 一、文档声明的定义与作用 文档声明,顾…...
光学涡旋干涉仪
一、什么是涡旋干涉仪? 涡旋光束一般指电场含有螺旋相位因子exp(iℓθ)的光束,其中ℓ为拓扑荷数,θ为方位角,其波前为螺旋形,光束中心存在相位奇点,因此涡旋光束的光强轮廓是中心强度为零的圆环。早在1992…...
Wireshark快速入门--对启动的后端程序进行抓包
怎么对自己启动的后端程序进行抓包? 1. 安装并启动 Wireshark 你要先从 Wireshark 官网 下载对应系统的安装包,然后进行安装。安装完成后,启动该软件。 可以快速入门可以参考博文:从零开始学 Wireshark:网络分析入门…...
ViTa-Zero:零样本视觉触觉目标 6D 姿态估计
25年4月来自Amazon 公司、Brown 大学和 Northestern 大学的论文“ViTa-Zero: Zero-shot Visuotactile Object 6D Pose Estimation”。 目标 6D 姿态估计是机器人技术中的一项关键挑战,尤其对于操作任务而言。虽然先前结合视觉和触觉(视觉触觉࿰…...
继承(c++版 非常详细版)
一. 继承的概念及定义 1.1 继承的概念 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有 类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。继…...
解锁服务器迁移的未来:《2025 服务器迁移效率白皮书》(附下载)
一、背景🏙️ 随着全球数字化转型的不断加速,企业在推动 IT 基础设施现代化过程中,面临着前所未有的服务器迁移挑战。传统的迁移工具和多云、混合云环境带来的复杂性,导致迁移效率低、成本高、人力投入大,从而严重阻碍…...
STM32的Flash映射双重机制
在STM32微控制器中,存在一个重要的内存映射特性:Flash存储器可以同时出现在两个不同的地址区域,而且可以通过重映射功能改变CPU启动时从哪个地址获取初始指令。 STM32的Flash映射双重机制 当描述"通常起始于地址0x00000000,…...
简单了解跨域问题
什么是跨域? 跨域是浏览器基于同源策略的安全机制。 如何两个请求之间,域名,端口,协议三者中有任意一个不同,就会产生跨域问题。 跨域的解决方案 1. CORS(跨源资源共享) 后端通过设置响应头声…...
sql学习笔记(四)
今天看到一个sql题,“近30天,******”,这里需要用到一个函数,date_add,其作用是在指定日期基础上添加一个时间间隔。 语法(以mysql为例): DATE_ADD(date, INTERVAL value unit) d…...
基于 Java 的实现前端组装查询语句,后端直接执行查询方案,涵盖前端和后端的设计思路
1. 前端设计 前端负责根据用户输入或交互条件,动态生成查询参数,并通过 HTTP 请求发送到后端。 前端逻辑: 提供用户界面(如表单、筛选器等),让用户选择查询条件。将用户选择的条件组装成 JSON 格式的查询参数。发送 HTTP 请求(如 POST 或 GET)到后端。示例: 假设用…...
反射与注解实现动态功能扩展案例-插件系统
学海无涯,志当存远。燃心砺志,奋进不辍。 愿诸君得此鸡汤,如沐春风,事业有成。 若觉此言甚善,烦请赐赞一枚,共励学途,同铸辉煌! 开发一个需要高度扩展性的应用,比如Web框…...
auto(x) decay copy
该提案为auto又增加了两个新语法:auto(x) 和auto{x}。两个作用一样,只是写法不同,都 是为x 创建一份拷贝。 为什么需要这么个东西?看一个例子: void bar(const auto&);void foo(const auto& param) {auto co…...
基于STM32、HAL库的DS2411R安全验证及加密芯片驱动程序设计
一、简介: DS2411R是Maxim Integrated(现为Analog Devices)生产的一款1-Wire硅序列号芯片,具有以下特点: 64位唯一ROM序列号(包括8位家族码、48位序列号和8位CRC校验码) 工作电压范围:2.8V至5.25V 工作温度范围:-40C至+85C 采用TO-92或SOT-223封装 通过1-Wire协议通信…...
疫苗接种体系进入“全生命周期”时代:公共卫生治理再提速
疫苗接种体系进入“全生命周期”时代:公共卫生治理再提速 在防控重大传染病的国家公共卫生战略中,疫苗接种始终处于基础性、先导性地位。2025年4月25日是第39个全国儿童预防接种日,活动主题为“打疫苗、防疾病、保健康”。近年来,…...