我们来详细分析 void l2c_rcv_acl_data(BT_HDR* p_msg)
函数的作用,它是 AOSP 中 L2CAP 层的一个关键入口函数,负责处理从 HCI 层收到的 ACL 数据包。这个函数非常核心,处理了从 HCI 到 L2CAP 再到具体通道的整个包接收逻辑。
1. l2c_rcv_acl_data
这个函数在 Bluetooth Stack 中的作用是:
接收来自 HCI 的 ACL 数据包 → 校验合法性 → 查找连接 → 根据 CID 转发到对应处理流程
// system/stack/l2cap/l2c_main.cc
void l2c_rcv_acl_data(BT_HDR* p_msg) {
/*
1. 解析基本头部信息(HCI)
1. 从 p_msg 中获取有效数据起始地址。
2. 从前两个字节解析出 HCI 句柄(handle)和包类型(pkt_type),句柄标识连接,pkt_type 表明是否是完整、起始、或继续包。
3. 此处的分包处理由 HCI 层已经完成,若发现是 continuation 类型,说明错误。
*/
uint8_t* p = (uint8_t*)(p_msg + 1) + p_msg->offset;
/* Extract the handle */
uint16_t handle;
STREAM_TO_UINT16(handle, p);
uint8_t pkt_type = HCID_GET_EVENT(handle);
handle = HCID_GET_HANDLE(handle);
/* Since the HCI Transport is putting segmented packets back together, we */
/* should never get a valid packet with the type set to "continuation" */
if (pkt_type == L2CAP_PKT_CONTINUE) {
L2CAP_TRACE_WARNING("L2CAP - received packet continuation");
osi_free(p_msg);
return;
}
/*
2. 验证 HCI 长度合法性
1. HCI 报文头部占 4 字节,后面紧跟 L2CAP 层数据。这里判断的是:
1. L2CAP 长度是否小于最小开销(4 字节)
2. hci_len 是否匹配实际消息长度(减去头部)
*/
uint16_t hci_len;
STREAM_TO_UINT16(hci_len, p);
if (hci_len < L2CAP_PKT_OVERHEAD || hci_len != p_msg->len - 4) {
/* Remote-declared packet size must match HCI_ACL size - ACL header (4) */
L2CAP_TRACE_WARNING("L2CAP - got incorrect hci header");
osi_free(p_msg);
return;
}
/*
3. 提取 L2CAP 长度与 Channel ID(CID)
1. l2cap_len:L2CAP 层报文长度(不含 L2CAP Header)
2. rcv_cid:L2CAP 通道标识符
*/
uint16_t l2cap_len, rcv_cid;
STREAM_TO_UINT16(l2cap_len, p);
STREAM_TO_UINT16(rcv_cid, p);
/*
4. 查找对应的 LCB(连接控制块)
1. 如果没有找到对应连接(可能是连接还没建立),进入「暂存逻辑」。
2. 对 signaling CID 的 Info Request/Connection Request 类型可以暂存起来(常见于车机作为 A2DP Sink 时处理 Source 的主动连接)
*/
/* Find the LCB based on the handle */
tL2C_LCB* p_lcb = l2cu_find_lcb_by_handle(handle); // 根据 HCI handle 查询对应的 LCB(Link Control Block)。 LCB 是 L2CAP 用于管理一个 物理链路(HCI连接)的控制结构体,包含对端地址、连接状态、通道集合等信息。
if (!p_lcb) { // 进入「暂存逻辑」
/*
这种情况很特殊,但在某些实际平台(比如 USB 蓝牙芯片)中可能会发生:
连接完成事件还没上报,L2CAP 却已经收到了 ACL 数据包。
这会造成一个「数据包先于连接初始化」的竞争问题。如果不处理会引发崩溃或数据丢失.
*/
/* There is a slight possibility (specifically with USB) that we get an */
/* L2CAP connection request before we get the HCI connection complete. */
/* So for these types of messages, hold them for up to 2 seconds. */
if (l2cap_len == 0) {// 接收到空包?直接丢弃
L2CAP_TRACE_WARNING("received empty L2CAP packet");
osi_free(p_msg);
return;
}
uint8_t cmd_code;
STREAM_TO_UINT8(cmd_code, p);// 提取命令类型字段, - 从 L2CAP Payload 中提取第一个字节 —— 这通常是 Signaling 命令的 Code(如 0x02 代表 Connection Request)。
/*
判断是否可以“暂存”包
layer_specific == 0:表示未处理状态,非重发或其他标记
rcv_cid == L2CAP_SIGNALLING_CID:必须是信令通道
cmd_code == L2CAP_CMD_INFO_REQ 或 L2CAP_CMD_CONN_REQ:只允许两种请求被暂存
换句话说:只有处于连接初始化阶段的控制命令才有资格被缓冲,其他一律丢弃。
*/
if ((p_msg->layer_specific != 0) || (rcv_cid != L2CAP_SIGNALLING_CID) ||
(cmd_code != L2CAP_CMD_INFO_REQ && cmd_code != L2CAP_CMD_CONN_REQ)) {
// 高通厂商调试场景:放行 QCOM 特例
bool qcom_debug_log = (handle == 3804 && ((rcv_cid & 0xff) == 0xff) &&
p_msg->layer_specific == 0);
if (!qcom_debug_log) {
// 如果满足 QCOM 特定调试条件,则不打印错误日志。
// 这属于平台相关的“容错分支”,可能是为了避开一些调试用 ACL 包。
L2CAP_TRACE_ERROR(
"L2CAP - rcvd ACL for unknown handle:%d ls:%d cid:%d opcode:%d cur "
"count:%d",
handle, p_msg->layer_specific, rcv_cid, cmd_code,
list_length(l2cb.rcv_pending_q));
}
osi_free(p_msg);
return;
}
// 暂存数据包 + 启动定时器
L2CAP_TRACE_WARNING(
"L2CAP - holding ACL for unknown handle:%d ls:%d cid:%d opcode:%d cur "
"count:%d",
handle, p_msg->layer_specific, rcv_cid, cmd_code,
list_length(l2cb.rcv_pending_q));
p_msg->layer_specific = 2;// 将数据包标记为「等待重处理」(layer_specific = 2)。
list_append(l2cb.rcv_pending_q, p_msg); // 添加进 l2cb.rcv_pending_q 队列。
if (list_length(l2cb.rcv_pending_q) == 1) {
alarm_set_on_mloop(l2cb.receive_hold_timer, BT_1SEC_TIMEOUT_MS,
l2c_receive_hold_timer_timeout, NULL); // 启动 1 秒定时器,用于后续重试(期望 HCI Connection Complete 在此期间到达)。 如果在定时器触发前收到了 Connection Completed,LCB 建立后会处理这些缓存包。
}
return;
}
/* Update the buffer header */
p_msg->offset += 4;
/* for BLE channel, always notify connection when ACL data received on the
* link */
/*
5. 对 BLE 链路:触发连接通知
*/
if (p_lcb && p_lcb->transport == BT_TRANSPORT_LE &&
p_lcb->link_state != LST_DISCONNECTING) {
/* only process fixed channel data as channel open indication when link is
* not in disconnecting mode */
l2cble_notify_le_connection(p_lcb->remote_bd_addr);
}
/*
6. 查找 CCB(Channel Control Block)
1. 只有当是动态 CID(即 L2CAP_BASE_APPL_CID 以上)才使用 p_ccb。
*/
/* Find the CCB for this CID */
tL2C_CCB* p_ccb = NULL;
if (rcv_cid >= L2CAP_BASE_APPL_CID) {
p_ccb = l2cu_find_ccb_by_cid(p_lcb, rcv_cid);
if (!p_ccb) {
L2CAP_TRACE_WARNING("L2CAP - unknown CID: 0x%04x", rcv_cid);
osi_free(p_msg);
return;
}
}
p_msg->len = hci_len - L2CAP_PKT_OVERHEAD;
p_msg->offset += L2CAP_PKT_OVERHEAD;
/*
7. 验证 L2CAP 报文长度是否一致
*/
if (l2cap_len != p_msg->len) {
L2CAP_TRACE_WARNING("L2CAP - bad length in pkt. Exp: %d Act: %d",
l2cap_len, p_msg->len);
osi_free(p_msg);
// 丢弃异常包
return;
}
/*
8. 不同类型的 CID 分发处理
*/
/* Send the data through the channel state machine */
if (rcv_cid == L2CAP_SIGNALLING_CID) { // Signaling CID(0x0001)
process_l2cap_cmd(p_lcb, p, l2cap_len); // 进入状态机处理命令(如连接请求、配置请求等)
osi_free(p_msg);
return;
}
if (rcv_cid == L2CAP_CONNECTIONLESS_CID) {
/* process_connectionless_data (p_lcb); */
osi_free(p_msg);
return;
}
if (rcv_cid == L2CAP_BLE_SIGNALLING_CID) { // BLE Signaling CID(0x0005)
l2cble_process_sig_cmd(p_lcb, p, l2cap_len);
osi_free(p_msg);
return;
}
if ((rcv_cid >= L2CAP_FIRST_FIXED_CHNL) &&
(rcv_cid <= L2CAP_LAST_FIXED_CHNL) &&
(l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb !=
NULL)) {// 固定通道处理(如 ATT、SMP 等)
/* only process fixed channel data when link is open or wait for data
* indication */
if (!p_lcb || p_lcb->link_state == LST_DISCONNECTING ||
!l2cu_initialize_fixed_ccb(p_lcb, rcv_cid)) {
osi_free(p_msg);
return;
}
/* If no CCB for this channel, allocate one */
p_ccb = p_lcb->p_fixed_ccbs[rcv_cid - L2CAP_FIRST_FIXED_CHNL];
p_ccb->metrics.rx(p_msg->len);
if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE)
l2c_fcr_proc_pdu(p_ccb, p_msg);
else
(*l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)(
rcv_cid, p_lcb->remote_bd_addr, p_msg);
return;
}
if (!p_ccb) {
osi_free(p_msg);
return;
}
if (p_lcb->transport == BT_TRANSPORT_LE) {// LE Credit-Based Channel(CID 动态分配)
l2c_lcc_proc_pdu(p_ccb, p_msg);
// 处理 credit 计数和补发机制
/* The remote device has one less credit left */
--p_ccb->remote_credit_count;
/* If the credits left on the remote device are getting low, send some */
if (p_ccb->remote_credit_count <= L2CA_LeCreditThreshold()) {
uint16_t credits = L2CA_LeCreditDefault() - p_ccb->remote_credit_count;
p_ccb->remote_credit_count = L2CA_LeCreditDefault();
/* Return back credits */
l2c_csm_execute(p_ccb, L2CEVT_L2CA_SEND_FLOW_CONTROL_CREDIT, &credits);
}
} else {
/* Basic mode packets go straight to the state machine */
// BR/EDR 动态通道(如 A2DP)
if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE)
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DATA, p_msg);
else {
/* eRTM or streaming mode, so we need to validate states first */
if ((p_ccb->chnl_state == CST_OPEN) || (p_ccb->chnl_state == CST_CONFIG))
l2c_fcr_proc_pdu(p_ccb, p_msg);
else
osi_free(p_msg);
}
}
}
1. l2cble_process_sig_cmd
process_l2cap_cmd
的核心——解析 L2CAP 信令数据包中的多个命令,并触发对应的状态机处理流程:
- 信令数据包的结构
- 多命令解析机制
- 命令处理的流程和入口函数
- 如何调用状态机执行具体动作
1. L2CAP 信令数据包结构
在 L2CAP
的信令通道中(CID 为 0x0001),一个数据包中可以包含多个信令命令,每个命令的结构如下:
字段 | 长度(字节) | 说明 |
---|---|---|
Code | 1 | 表示命令类型,比如 L2CAP_CMD_CONN_REQ 等 |
Identifier | 1 | 用于区分多个命令实例 |
Length | 2 | 表示后续数据部分的长度 |
Data | N | 命令的具体参数,根据 Code 不同而不同 |
一个完整的 L2CAP 信令包的数据部分可能是多个这样的命令连续拼接。 |
举例:
# 车机接收到 pts 连接 avdtp 的连接请求
967 2025-04-22 15:37:30.346856 Vencer_f4:b1:83 (PTS-A2DP-B183) 22:22:96:de:b1:39 (leo 8295 chan) L2CAP 17 Rcvd Connection Request (AVDTP, SCID: 0x0040)
Bluetooth L2CAP Protocol
Length: 8
CID: L2CAP Signaling Channel (0x0001)
Command: Connection Request
Command Code: Connection Request (0x02) // 1 个字节
Command Identifier: 0x02 // 1 个字节
Command Length: 4 // 两个字节
PSM: AVDTP (0x0019) // data 部分
Source CID: Dynamically Allocated Channel (0x0040)
[Disconnect in frame: 0]
2. process_l2cap_cmd
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;
}
}
}
1. L2CAP_CMD_ 解析
l2cap 命令解析, 接下来我们 每一个命令来解析, 都做了那些事情。
// system/stack/include/l2cdefs.h
#define L2CAP_CMD_REJECT 0x01
#define L2CAP_CMD_CONN_REQ 0x02
#define L2CAP_CMD_CONN_RSP 0x03
#define L2CAP_CMD_CONFIG_REQ 0x04
#define L2CAP_CMD_CONFIG_RSP 0x05
#define L2CAP_CMD_DISC_REQ 0x06
#define L2CAP_CMD_DISC_RSP 0x07
#define L2CAP_CMD_ECHO_REQ 0x08
#define L2CAP_CMD_ECHO_RSP 0x09
#define L2CAP_CMD_INFO_REQ 0x0A
#define L2CAP_CMD_INFO_RSP 0x0B
#define L2CAP_CMD_AMP_CONN_REQ 0x0C
#define L2CAP_CMD_AMP_MOVE_REQ 0x0E
#define L2CAP_CMD_BLE_UPDATE_REQ 0x12
#define L2CAP_CMD_BLE_UPDATE_RSP 0x13
#define L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ 0x14
#define L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES 0x15
#define L2CAP_CMD_BLE_FLOW_CTRL_CREDIT 0x16
/* Enhanced CoC */
#define L2CAP_CMD_CREDIT_BASED_CONN_REQ 0x17
#define L2CAP_CMD_CREDIT_BASED_CONN_RES 0x18
#define L2CAP_CMD_CREDIT_BASED_RECONFIG_REQ 0x19
#define L2CAP_CMD_CREDIT_BASED_RECONFIG_RES 0x1A
命令名称 | 十六进制值 | 使用场景说明 | 所属协议类型 |
---|---|---|---|
L2CAP_CMD_REJECT | 0x01 | 用于拒绝非法或无法处理的 L2CAP 命令(如不支持的请求)。 | 通用(BR/EDR/LE) |
L2CAP_CMD_CONN_REQ | 0x02 | 请求在某个 PSM 上建立 L2CAP 信道。 | BR/EDR |
L2CAP_CMD_CONN_RSP | 0x03 | 响应连接请求,指示连接是否成功以及当前状态。 | BR/EDR |
L2CAP_CMD_CONFIG_REQ | 0x04 | 请求对已建立的信道进行参数协商(如 MTU、延迟等)。 | BR/EDR |
L2CAP_CMD_CONFIG_RSP | 0x05 | 响应配置信道请求,返回是否接受配置。 | BR/EDR |
L2CAP_CMD_DISC_REQ | 0x06 | 请求断开某个逻辑信道。 | BR/EDR |
L2CAP_CMD_DISC_RSP | 0x07 | 响应断开请求。 | BR/EDR |
L2CAP_CMD_ECHO_REQ | 0x08 | 回显测试命令,用于链路检测或诊断。 | 通用(BR/EDR/LE) |
L2CAP_CMD_ECHO_RSP | 0x09 | 回应回显请求。 | 通用(BR/EDR/LE) |
L2CAP_CMD_INFO_REQ | 0x0A | 请求远端设备的 L2CAP 支持信息(如扩展功能、固定信道等)。 | BR/EDR |
L2CAP_CMD_INFO_RSP | 0x0B | 响应信息请求。 | BR/EDR |
L2CAP_CMD_AMP_CONN_REQ | 0x0C | 在 AMP(增强型多通道传输)环境下请求连接。 | AMP(高带宽通道) |
L2CAP_CMD_AMP_MOVE_REQ | 0x0E | 请求将信道从一个控制器移动到另一个(如从 BR/EDR 切换到 AMP)。 | AMP |
L2CAP_CMD_BLE_UPDATE_REQ | 0x12 | 在 BLE 连接中请求更新连接参数(如间隔、延迟等)。 | LE |
L2CAP_CMD_BLE_UPDATE_RSP | 0x13 | 响应 BLE 连接参数更新请求。 | LE |
L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ | 0x14 | 请求建立基于信用的 BLE L2CAP 信道(LE CoC 模式)。 | LE |
L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES | 0x15 | 响应 BLE 信用型连接请求。 | LE |
L2CAP_CMD_BLE_FLOW_CTRL_CREDIT | 0x16 | BLE 信道的数据流控(添加信用额度),用于信用机制的数据发送控制。 | LE |
L2CAP_CMD_CREDIT_BASED_CONN_REQ | 0x17 | Enhanced Credit Based Flow Control(ECFC) 模式下的连接请求。 | BR/EDR(ECFC) |
L2CAP_CMD_CREDIT_BASED_CONN_RES | 0x18 | ECFC 连接响应。 | BR/EDR(ECFC) |
L2CAP_CMD_CREDIT_BASED_RECONFIG_REQ | 0x19 | 请求重新配置 ECFC 信道的参数(如 MTU、MPS)。 | BR/EDR(ECFC) |
L2CAP_CMD_CREDIT_BASED_RECONFIG_RES | 0x1A | 响应 ECFC 信道重配置请求。 | BR/EDR(ECFC) |
2. L2CAP_CFG_ 解析
// system/stack/include/l2cdefs.h
#define L2CAP_CFG_TYPE_MTU 0x01
#define L2CAP_CFG_TYPE_FLUSH_TOUT 0x02
#define L2CAP_CFG_TYPE_QOS 0x03
#define L2CAP_CFG_TYPE_FCR 0x04
#define L2CAP_CFG_TYPE_FCS 0x05
#define L2CAP_CFG_TYPE_EXT_FLOW 0x06
- 这些选项通常在
L2CAP_CMD_CONFIG_REQ
和L2CAP_CMD_CONFIG_RSP
命令中被使用,用于协商逻辑信道的参数:
配置项宏定义 | 十六进制值 | 配置名称 | 使用场景说明 |
---|---|---|---|
L2CAP_CFG_TYPE_MTU | 0x01 | 最大传输单元(MTU) | 指定对端每个数据包允许的最大长度。用于分段策略,确保数据不过大。典型场景如音视频数据传输。 |
L2CAP_CFG_TYPE_FLUSH_TOUT | 0x02 | 刷新超时(Flush Timeout) | 指定数据包在控制器缓存中可等待的最大时间。超时未发送将被丢弃。常用于流媒体传输场景降低延迟。 |
L2CAP_CFG_TYPE_QOS | 0x03 | 服务质量(QoS) | 用于为信道指定延迟、吞吐量、可靠性等服务质量参数。适用于语音或视频等对时延敏感的应用。 |
L2CAP_CFG_TYPE_FCR | 0x04 | 流控制和重传(FCR) | 启用 Enhanced Retransmission Mode 或 Streaming Mode。控制可靠性和拥塞管理。用于高可靠性场景。 |
L2CAP_CFG_TYPE_FCS | 0x05 | 帧校验序列(FCS) | 用于启用/禁用帧校验(Frame Check Sequence),提升数据完整性。常用于对稳定性要求高的连接。 |
L2CAP_CFG_TYPE_EXT_FLOW | 0x06 | 扩展流控(Extended Flow Spec) | 指定更详细的流控参数,如服务类别、最大延迟、最小带宽等。适用于复杂流控策略或定制化传输需求。 |
应用举例: |
- A2DP(音频流) 通常会协商 MTU 和 Flush Timeout;
- HID(蓝牙键鼠) 可能会协商 QoS;
- RFCOMM 使用 FCR 来增强重传机制以提供可靠串口仿真;
- 文件传输(如 OPP)可能启用 FCS 以保障数据完整性。
3. 各个命令详细处理
1. L2CAP_CMD_REJECT
这段代码处理 L2CAP 信令通道上的 Reject(拒绝)命令。Reject 通常意味着对端拒绝了某个 L2CAP 命令(比如连接请求、配置请求等),需要查看拒绝的原因并决定如何应对。
case L2CAP_CMD_REJECT:
uint16_t rej_reason; // 声明变量,用于存储 Reject 的原因码。
if (p + 2 > p_next_cmd) { // 检查剩余数据长度是否足以读取 2 字节的 rej_reason。如果不足,认为数据损坏或格式错误,直接返回。
LOG_WARN("Not enough data for L2CAP_CMD_REJECT");
return;
}
STREAM_TO_UINT16(rej_reason, p); // 从数据流 p 中提取出 2 字节的拒绝原因码,存入 rej_reason。
// 第一种拒绝原因:MTU 超出限制
if (rej_reason == L2CAP_CMD_REJ_MTU_EXCEEDED) {
// 这个拒绝原因表示我们发给对端的 PDU 超出了对端支持的最大 MTU。
uint16_t rej_mtu;
if (p + 2 > p_next_cmd) { // 继续检查是否还有足够空间读取 MTU 值。
LOG_WARN("Not enough data for L2CAP_CMD_REJ_MTU_EXCEEDED");
return;
}
STREAM_TO_UINT16(rej_mtu, p);// 读取对端反馈的 MTU 值(即它支持的最大 PDU 长度)。
/* What to do with the MTU reject ? We have negotiated an MTU. For now
* we will ignore it and let a higher protocol timeout take care of it
*/
// 打印警告日志,提示收到 MTU 拒绝,但下面的注释说明它 暂时不会调整逻辑或重试,而是让上层协议自行超时处理。
LOG_WARN("MTU rej Handle: %d MTU: %d", p_lcb->Handle(), rej_mtu);
}
// 第二种拒绝原因:CID 无效
if (rej_reason == L2CAP_CMD_REJ_INVALID_CID) { // 对端拒绝的原因是我们给它发的某个信道 ID(CID)是无效的。
uint16_t lcid, rcid;
if (p + 4 > p_next_cmd) {
LOG_WARN("Not enough data for L2CAP_CMD_REJ_INVALID_CID");
return;
}
// 继续读取两个 CID(Local 和 Remote 的)。
STREAM_TO_UINT16(rcid, p);
STREAM_TO_UINT16(lcid, p);
// 记录无效信道的日志。
LOG_WARN("Rejected due to invalid CID, LCID: 0x%04x RCID: 0x%04x",
lcid, rcid);
/* Remote CID invalid. Treat as a disconnect */
// 通过 Local CID 查找对应的 L2CAP 控制块(CCB)。
tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
if ((p_ccb != NULL) && (p_ccb->remote_cid == rcid)) {
// 如果找到了对应的 CCB 且远端 CID 匹配,模拟处理为“断开连接”。也就是说,这种情况下我们不尝试修复,而是让上层当成连接断开处理。
/* Fake link disconnect - no reply is generated */
LOG_WARN("Remote CID is invalid, treat as disconnected");
l2c_csm_execute(p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL);
}
}
// 第三种拒绝原因:不理解的命令(兼容某些厂商实现)
/* SonyEricsson Info request Bug workaround (Continue connection) */
else if (rej_reason == L2CAP_CMD_REJ_NOT_UNDERSTOOD &&
p_lcb->w4_info_rsp) {
// 这个条件是个 厂商兼容性处理(例如 SonyEricsson):当对方说“我不理解这个命令”,而我们正等待 info_rsp,说明这个设备可能不支持 INFO_REQ 命令。
alarm_cancel(p_lcb->info_resp_timer); // 取消等待 info_rsp 的定时器,并清除标记,避免一直等待。
p_lcb->w4_info_rsp = false;
// 构造一个 假的 Info Rsp,用来通知所有 channel:连接继续正常。
tL2C_CONN_INFO ci;
ci.status = HCI_SUCCESS;
ci.bd_addr = p_lcb->remote_bd_addr;
/* For all channels, send the event through their FSMs */
for (tL2C_CCB* p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb;
p_ccb = p_ccb->p_next_ccb) {
// 遍历所有正在使用该 LCB 的信道控制块 CCB,通知它们 Info 请求已“完成”,可以继续连接流程。
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci);
}
}
break;
拒绝原因宏定义 | 使用场景说明 | 对应行为 |
---|---|---|
L2CAP_CMD_REJ_MTU_EXCEEDED | 请求 PDU 长度超过了对端允许的最大 MTU | 打日志,暂时不处理,等待上层超时 |
L2CAP_CMD_REJ_INVALID_CID | 指定的信道 ID 无效,可能信道已经被关闭 | 视为连接断开,执行断开流程 |
L2CAP_CMD_REJ_NOT_UNDERSTOOD | 对端不支持该命令,通常是老设备或厂商定制协议(如 Info Req) | 模拟 Info 响应继续连接流程 |
2. L2CAP_CMD_CONN_REQ
在 L2CAP 信令通道上收到对端的 连接请求命令(0x02) 后,解析参数、判断是否支持该 PSM(协议服务多路复用器),若支持则分配控制块(CCB)并交由状态机处理连接流程,否则发送拒绝响应。
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);
STREAM_TO_UINT16(rcid, p);
// 查找是否有注册过该 PSM 的服务(RCB = Registration Control Block),PSM 就像是协议层的端口号,只有注册了的 PSM 才能接受连接。
tL2C_RCB* p_rcb = l2cu_find_rcb_by_psm(con_info.psm);
// 检查是否未找到服务,或者 PSM 为 BT_PSM_ATT(ATT 协议是 GATT 使用的,但不能通过传统 L2CAP 信令连接,只能走 LE 信道)。
if (!p_rcb || con_info.psm == BT_PSM_ATT) {
// 如果不支持此 PSM,则发送 Reject Response,原因是 NO_PSM,表明本地没有为该 PSM 注册服务。
LOG_WARN("Rcvd conn req for unknown PSM: %d", con_info.psm);
l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_PSM);
break;
} else {
if (!p_rcb->api.pL2CA_ConnectInd_Cb) {
// 如果找到了注册信息,但该 PSM 是**仅用于发起连接(即不接受被动连接)**,也拒绝连接。这个回调函数 pL2CA_ConnectInd_Cb 为 NULL,说明应用没有设置处理被动连接请求的回调。
LOG_WARN("Rcvd conn req for outgoing-only connection PSM: %d",
con_info.psm);
l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_PSM);
break;
}
}
// 尝试为此次连接请求分配一个新的 Channel Control Block(信道控制块),这代表一次独立的 L2CAP 信道。
tL2C_CCB* p_ccb = l2cu_allocate_ccb(p_lcb, 0);
if (p_ccb == nullptr) {
// 如果系统资源不足,分配失败,返回资源不足的拒绝响应。
LOG_ERROR("Unable to allocate CCB");
l2cu_reject_connection(p_lcb, rcid, id, L2CAP_CONN_NO_RESOURCES);
break;
}
// 初始化 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;
}
步骤 | 动作说明 |
---|---|
安全检查 | 确保数据长度合法,能解析 PSM + Remote CID |
提取参数 | 从信令中读取 PSM 和对端分配的 CID |
查找服务 | 是否注册了该 PSM(支持该协议) |
检查是否可被动接收 | 若 PSM 仅支持主动连接(无回调),拒绝连接 |
分配控制块 | 为该连接分配 CCB,如失败则返回资源不足 |
初始化控制块 | 设置 CCB 的 PSM、远端 CID、是否对端发起等 |
启动状态机 | 执行连接请求事件,进入连接响应阶段 |
3.L2CAP_CMD_CONN_RSP
此命令是连接响应,用于回应连接请求命令(L2CAP_CMD_CONN_REQ)。对端设备在接收到连接请求后,会发送该命令,表示是否接受连接、连接状态等。
case L2CAP_CMD_CONN_RSP: {
uint16_t lcid; // lcid(Local CID),即本地在发起连接请求时分配的 Channel ID。
/*
安全检查:连接响应命令包含 8 字节:
Remote CID(2)
Local CID(2)
Result(2)
Status(2)
*/
if (p + 8 > p_next_cmd) {
// 如果数据不足,记录警告,提前返回。
LOG_WARN("Not enough data for L2CAP_CMD_CONN_REQ"); // log 里面写错了应该是 L2CAP_CMD_CONN_RSP
return;
}
/*
从信令包中解析出连接响应参数:
remote_cid:对端分配的 CID,后续数据收发将使用它。
lcid:我们当初发送连接请求时分配的本地 CID。
result:连接结果(成功、等待中、拒绝等)。
status:如果 result 是 Pending,则 status 指示原因。
*/
STREAM_TO_UINT16(con_info.remote_cid, p);
STREAM_TO_UINT16(lcid, p);
STREAM_TO_UINT16(con_info.l2cap_result, p);
STREAM_TO_UINT16(con_info.l2cap_status, p);
// 查找本地为此连接分配的控制块 CCB,依据本地 CID(lcid)来索引。
tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
if (!p_ccb) {
// 如果找不到对应的 CCB,说明可能状态不一致,记录告警,忽略此响应。
LOG_WARN("no CCB for conn rsp, LCID: %d RCID: %d", lcid,
con_info.remote_cid);
break;
}
if (p_ccb->local_id != id) {
// 对比当前信令响应包的 id 与 CCB 中记录的 local_id(发出请求时保存的 ID)。若不一致,说明不是对应的响应,记录警告并跳出。
LOG_WARN("con rsp - bad ID. Exp: %d Got: %d", p_ccb->local_id, id);
break;
}
if (con_info.l2cap_result == L2CAP_CONN_OK)
// 若 result == OK(连接成功),则通过事件 L2CEVT_L2CAP_CONNECT_RSP 交由 CCB 状态机处理连接建立后的流程(如进入配置阶段)。
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP, &con_info);
else if (con_info.l2cap_result == L2CAP_CONN_PENDING)
// 若结果是 Pending,表示对端还在处理中,状态机会进入等待状态。
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_PND, &con_info);
else
// 其余结果都视为连接被拒绝或失败,进入 NEG(Negative)路径,状态机会做清理或通知上层失败。
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_NEG, &con_info);
break;
}
步骤 | 操作描述 |
---|---|
安全检查 | 确保信令包长度至少 8 字节 |
参数提取 | 读取对端 CID、本地 CID、连接结果、状态码 |
查找本地 CCB | 依据本地 CID 查找控制块 |
验证信令 ID | 确认响应 ID 与请求一致 |
分支处理连接结果 | 根据连接结果(成功/等待/失败)进入状态机 |
L2CAP 连接结果码 | 对应事件 | 意义 |
---|---|---|
L2CAP_CONN_OK | L2CEVT_L2CAP_CONNECT_RSP | 连接成功,进入配置阶段 |
L2CAP_CONN_PENDING | L2CEVT_L2CAP_CONNECT_RSP_PND | 对端还在处理,等待后续回应 |
其他 | L2CEVT_L2CAP_CONNECT_RSP_NEG | 连接失败,释放资源,通知上层 |
4.L2CAP_CMD_CONFIG_REQ
L2CAP 配置请求:
- 连接建立后(即
Connection Response
成功),L2CAP 层会互相发送Configuration Request
。 - 内容包括:MTU、流控方式、QoS、FCS 等。
- 响应方式:对端必须用
Configuration Response
回应。
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;
}
5.L2CAP_CMD_CONFIG_RSP
表示当前处理的是 配置响应命令(Command Code 0x05)。
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:本地通道 ID
cfg_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 Timeout
cfg_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 * 4
LOG_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 * 2
LOG_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_NEG
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_RSP_NEG, &cfg_info);
}
} else {
// 找不到则丢弃
// 输出日志说明:收到一个对未知通道的配置响应,可能是协议异常或状态不同步。
LOG_WARN("Rcvd cfg rsp for unknown CID: 0x%04x", lcid);
}
break;
}
步骤 | 功能 |
---|---|
数据校验 | 确保命令结构完整性 |
参数解析 | 逐个字段解析配置内容 |
查找 CCB | 通过 LCID 定位连接上下文 |
校验 ID | 验证是否响应的是我们发出的请求 |
状态机事件 | 成功响应和失败响应分别上报不同事件 |
6.L2CAP_CMD_DISC_REQ
代表当前处理的命令是 Disconnect Request(断开请求)。
- 远端设备主动 要求断开
case L2CAP_CMD_DISC_REQ: {
uint16_t lcid, rcid; // lcid: Local CID(本地通道 ID); rcid: Remote CID(远程通道 ID)
if (p + 4 > p_next_cmd) { // 这两个字段是 Disconnect Request 中的标准内容,分别占用 2 字节。
// 检查是否有足够的 4 字节数据来读取 LCID 和 RCID。
// 若数据不足,说明数据包不完整,记录日志并终止处理。
LOG_WARN("Not enough data for L2CAP_CMD_DISC_REQ");
return;
}
// 解析出断开请求中的本地和远程 CID。
STREAM_TO_UINT16(lcid, p);
STREAM_TO_UINT16(rcid, p);
// 在当前连接控制块表(CCB Table)中查找指定本地 CID 的连接。
// p_lcb 是当前的物理连接控制块(Link Control Block),代表当前 L2CAP 链路。
tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
if (p_ccb) {
// 如果找到了对应的通道控制块(CCB),说明当前存在此通道。
if (p_ccb->remote_cid == rcid) {
// 避免被错误或恶意数据干扰。
// 双向校验:确认远端发来的 rcid 和当前通道中记录的远端 CID 一致
p_ccb->remote_id = id; // 保存远端发送该断开请求所使用的 命令 ID,稍后用于发送响应(Disconnect Response)。
// 发送一个 L2CEVT_L2CAP_DISCONNECT_REQ 事件给该通道对应的状态机。
// 状态机会处理该事件,进入断开流程。
// &con_info 是附带的连接信息结构体(通常包含上下文或参数)。
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DISCONNECT_REQ, &con_info);
}
} else
// 如果没找到对应 CCB 或 rcid 不匹配,说明通道无效或不同步。
// 为了不阻塞远端流程,我们依然发一个 Disconnect Response 表示接受。
l2cu_send_peer_disc_rsp(p_lcb, id, lcid, rcid);
break;
}
步骤 | 内容 |
---|---|
校验数据 | 至少需要 4 字节才能解析 LCID 和 RCID |
查找连接 | 根据 LCID 查找本地控制块(CCB) |
校验 RCID | 防止 CID 错误或欺骗 |
状态机事件 | 触发状态机断开请求事件 |
自动回应 | 如果 CCB 无效,也会直接发回应给对端,防止其等待超时 |
7.L2CAP_CMD_DISC_RSP
这个分支对应的是远端设备对我们之前发出的 L2CAP_CMD_DISC_REQ
命令的响应。
L2CAP Disconnect Response(命令码 0x07)
case L2CAP_CMD_DISC_RSP: {
uint16_t lcid, rcid;
if (p + 4 > p_next_cmd) {
// 确保有足够的 4 字节可读取
// 如果数据不够,说明数据包不完整,打印警告并提前退出
LOG_WARN("Not enough data for L2CAP_CMD_DISC_RSP");
return;
}
// 注意此处的顺序和 Disconnect Request 相反,协议定义的顺序是先 RCID,再 LCID。
// 从数据中解析出远端 CID 和本地 CID
STREAM_TO_UINT16(rcid, p);
STREAM_TO_UINT16(lcid, p);
// 根据本地 CID 查找对应的通道控制块(CCB)
tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
if (p_ccb) {
// 如果找到对应通道:
/*
进行 双重校验:
1. remote_cid == rcid:确保远端 CID 匹配,确认是我们发出的断开请求的响应。
2. local_id == id:确认这个响应对应我们发出的那个命令(根据命令 ID 匹配)。
*/
if ((p_ccb->remote_cid == rcid) && (p_ccb->local_id == id)) {
/*
1. 向通道状态机发送事件 L2CEVT_L2CAP_DISCONNECT_RSP,表示我们收到了断开响应。
2. 状态机会根据当前状态执行相应的清理或状态迁移(通常进入 CLOSED 状态,释放资源等)
*/
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DISCONNECT_RSP, &con_info);
}
}
break;
}
步骤 | 描述 |
---|---|
验证数据长度 | 确保读取 rcid 和 lcid 时不会越界 |
查找通道 | 用 lcid 找到本地通道控制块(CCB) |
双重匹配验证 | remote_cid == rcid && local_id == id ,防止响应伪造或匹配错误 |
状态机触发 | 成功匹配后交给状态机处理 L2CAP_DISCONNECT_RSP 事件 |
- 假设我们是手机(A2DP Source)向车机发出断开请求后,车机回应断开响应,这段代码就是手机收到回应后的处理逻辑。
- 类似于你提出“要断开连接”后,对方确认“好的我已断开”,我们再关闭这个通道。
8. L2CAP_CMD_ECHO_REQ
- Echo Request 是一种“心跳机制”,常被用来检测对端是否仍然在线(比如控制层 ping)。
- 对端发来 Echo Request,本地直接回 Echo Response,内容通常原封不动返回。
case L2CAP_CMD_ECHO_REQ:
l2cu_send_peer_echo_rsp(p_lcb, id, p, cmd_len);
break;
l2cu_send_peer_echo_rsp(…):
-
参数含义:
- p_lcb: 当前的连接控制块,表示哪个链接发来的请求。
- id: 这条 Echo 请求的信令 ID,响应时必须一致。
- p: 指向请求中的数据负载。
- cmd_len: 请求的数据长度。
-
响应函数内部会构造
L2CAP_CMD_ECHO_RSP
(0x09)信令包,并将数据原样回送。
类似场景:
- A 对 B 说:“你还在线吗?” →
Echo Request
- B 回答:“我在,我能听见。” →
Echo Response
9.L2CAP_CMD_INFO_REQ
Info Request 是用来向对端询问信息类型支持情况的命令,比如:
- 你支持哪些固定信道?
- 你支持哪些扩展特性?
- 等等
当前是 远端设备, 来询问我们 都支持那些信息。
case L2CAP_CMD_INFO_REQ: {
uint16_t info_type;
if (p + 2 > p_next_cmd) {
// 请求中只有一个 2 字节字段,表示需要查询的“信息类型”
// 若数据不足 2 字节,则丢弃请求。
LOG_WARN("Not enough data for L2CAP_CMD_INFO_REQ");
return;
}
STREAM_TO_UINT16(info_type, p);
l2cu_send_peer_info_rsp(p_lcb, id, info_type);
break;
}
l2cu_send_peer_info_rsp:
- 根据
info_type
类型,发送相应的信息响应包(L2CAP_CMD_INFO_RSP
,0x0B) - 会调用内部逻辑填充相应内容,如:
L2CAP_EXTENDED_FEATURES_INFO_TYPE (0x0001)
L2CAP_FIXED_CHANNELS_INFO_TYPE (0x0002)
信息响应的内容和结果码(成功或不支持)依赖系统本身支持的功能。
类似场景:
- A 问 B:“你支持哪些高级功能?” →
Info Request
- B 回复:“我支持固定信道 0x02、扩展功能 X/Y/Z…” →
Info Response
10.L2CAP_CMD_INFO_RSP
L2CAP_CMD_INFO_RSP(信息响应)命令的处理逻辑,这是在 L2CAP 信令通道中用于获取远端设备功能信息的一个关键命令。
处理的是 L2CAP 信息响应(0x0B) 命令,一般用于响应 L2CAP_CMD_INFO_REQ 请求(比如询问对端是否支持扩展功能、固定信道等)。
case L2CAP_CMD_INFO_RSP:
/* Stop the link connect timer if sent before L2CAP connection is up */
// 如果当前 Link Control Block (p_lcb) 正在等待 Info Response:
if (p_lcb->w4_info_rsp) {
alarm_cancel(p_lcb->info_resp_timer); // 取消等待的定时器 info_resp_timer
p_lcb->w4_info_rsp = false; // 清除等待标志 w4_info_rsp
// 这样处理事为了: 防止因为超时机制触发错误处理,现在响应已经收到了,当然不需要再等了。
}
// 解析信息类型和结果
uint16_t info_type, result;
if (p + 4 > p_next_cmd) {
LOG_WARN("Not enough data for L2CAP_CMD_INFO_RSP");
return;
}
// 从数据中读取 info_type(查询类型) 和 result(响应结果)
STREAM_TO_UINT16(info_type, p);
STREAM_TO_UINT16(result, p);
if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) &&
(result == L2CAP_INFO_RESP_RESULT_SUCCESS)) {
// 如果是扩展功能的响应,并且返回成功:
if (p + 4 > p_next_cmd) {
LOG_WARN("Not enough data for L2CAP_CMD_INFO_RSP sub_event");
return;
}
// 从包中读取 4 字节,表示远端支持的扩展特性(用位图表示)
STREAM_TO_UINT32(p_lcb->peer_ext_fea, p);
// 如果远端支持固定信道(如 AMP Manager、LE 信道等):
if (p_lcb->peer_ext_fea & L2CAP_EXTFEA_FIXED_CHNLS) {
// 向对方继续请求其固定信道支持信息(info_type = L2CAP_FIXED_CHANNELS_INFO_TYPE)
l2cu_send_peer_info_req(p_lcb, L2CAP_FIXED_CHANNELS_INFO_TYPE);
break;
} else {
// 否则,直接走固定信道响应的处理流程
l2cu_process_fixed_chnl_resp(p_lcb);
}
}
// 处理固定信道支持信息
// 该掩码可以告诉我们对端是否支持某些固定信道,如信令(0x02)、连接控制(0x03)、BLE ATT(0x04)等。
if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) {
// 这是对固定信道的查询响应:
if (result == L2CAP_INFO_RESP_RESULT_SUCCESS) {
if (p + L2CAP_FIXED_CHNL_ARRAY_SIZE > p_next_cmd) {
return;
}
// 若成功,则将固定信道掩码(bitmap)复制到 `p_lcb->peer_chnl_mask` 中
memcpy(p_lcb->peer_chnl_mask, p, L2CAP_FIXED_CHNL_ARRAY_SIZE);
}
// 调用 l2cu_process_fixed_chnl_resp 统一处理。
l2cu_process_fixed_chnl_resp(p_lcb);
}
// 通知所有等待 Info 的通道
{
tL2C_CONN_INFO ci;// 构造连接信息结构 tL2C_CONN_INFO
ci.status = HCI_SUCCESS;
ci.bd_addr = p_lcb->remote_bd_addr;
// 遍历 p_lcb 上挂载的所有 Channel Control Block (CCB)
for (tL2C_CCB* p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb;
p_ccb = p_ccb->p_next_ccb) {
// 通知它们:已收到 Info Response
// 用 L2CEVT_L2CAP_INFO_RSP 触发状态机行为
l2c_csm_execute(p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci);
}
}
break;
收到 L2CAP_CMD_INFO_RSP
│
├─ 判断是否在等待响应 → 是 → 取消定时器
│
├─ 解析 info_type + result
│
├─ info_type == EXTENDED_FEATURES?
│ ├─ 成功 → 读取扩展功能位图
│ ├─ 检查是否支持固定信道 → 是 → 继续请求 → break
│ └─ 否 → 调用 l2cu_process_fixed_chnl_resp
│
├─ info_type == FIXED_CHANNELS?
│ ├─ 成功 → 保存通道掩码
│ └─ 调用 l2cu_process_fixed_chnl_resp
│
└─ 通知所有 CCB:已收到 info_rsp