【android bluetooth 协议分析 06】【l2cap详解 7】【l2c_rcv_acl_data函数解析】

我们来详细分析 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),一个数据包中可以包含多个信令命令,每个命令的结构如下:

字段长度(字节)说明
Code1表示命令类型,比如 L2CAP_CMD_CONN_REQ
Identifier1用于区分多个命令实例
Length2表示后续数据部分的长度
DataN命令的具体参数,根据 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_REJECT0x01用于拒绝非法或无法处理的 L2CAP 命令(如不支持的请求)。通用(BR/EDR/LE)
L2CAP_CMD_CONN_REQ0x02请求在某个 PSM 上建立 L2CAP 信道。BR/EDR
L2CAP_CMD_CONN_RSP0x03响应连接请求,指示连接是否成功以及当前状态。BR/EDR
L2CAP_CMD_CONFIG_REQ0x04请求对已建立的信道进行参数协商(如 MTU、延迟等)。BR/EDR
L2CAP_CMD_CONFIG_RSP0x05响应配置信道请求,返回是否接受配置。BR/EDR
L2CAP_CMD_DISC_REQ0x06请求断开某个逻辑信道。BR/EDR
L2CAP_CMD_DISC_RSP0x07响应断开请求。BR/EDR
L2CAP_CMD_ECHO_REQ0x08回显测试命令,用于链路检测或诊断。通用(BR/EDR/LE)
L2CAP_CMD_ECHO_RSP0x09回应回显请求。通用(BR/EDR/LE)
L2CAP_CMD_INFO_REQ0x0A请求远端设备的 L2CAP 支持信息(如扩展功能、固定信道等)。BR/EDR
L2CAP_CMD_INFO_RSP0x0B响应信息请求。BR/EDR
L2CAP_CMD_AMP_CONN_REQ0x0C在 AMP(增强型多通道传输)环境下请求连接。AMP(高带宽通道)
L2CAP_CMD_AMP_MOVE_REQ0x0E请求将信道从一个控制器移动到另一个(如从 BR/EDR 切换到 AMP)。AMP
L2CAP_CMD_BLE_UPDATE_REQ0x12在 BLE 连接中请求更新连接参数(如间隔、延迟等)。LE
L2CAP_CMD_BLE_UPDATE_RSP0x13响应 BLE 连接参数更新请求。LE
L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ0x14请求建立基于信用的 BLE L2CAP 信道(LE CoC 模式)。LE
L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES0x15响应 BLE 信用型连接请求。LE
L2CAP_CMD_BLE_FLOW_CTRL_CREDIT0x16BLE 信道的数据流控(添加信用额度),用于信用机制的数据发送控制。LE
L2CAP_CMD_CREDIT_BASED_CONN_REQ0x17Enhanced Credit Based Flow Control(ECFC) 模式下的连接请求。BR/EDR(ECFC)
L2CAP_CMD_CREDIT_BASED_CONN_RES0x18ECFC 连接响应。BR/EDR(ECFC)
L2CAP_CMD_CREDIT_BASED_RECONFIG_REQ0x19请求重新配置 ECFC 信道的参数(如 MTU、MPS)。BR/EDR(ECFC)
L2CAP_CMD_CREDIT_BASED_RECONFIG_RES0x1A响应 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_REQL2CAP_CMD_CONFIG_RSP 命令中被使用,用于协商逻辑信道的参数:
配置项宏定义十六进制值配置名称使用场景说明
L2CAP_CFG_TYPE_MTU0x01最大传输单元(MTU)指定对端每个数据包允许的最大长度。用于分段策略,确保数据不过大。典型场景如音视频数据传输。
L2CAP_CFG_TYPE_FLUSH_TOUT0x02刷新超时(Flush Timeout)指定数据包在控制器缓存中可等待的最大时间。超时未发送将被丢弃。常用于流媒体传输场景降低延迟。
L2CAP_CFG_TYPE_QOS0x03服务质量(QoS)用于为信道指定延迟、吞吐量、可靠性等服务质量参数。适用于语音或视频等对时延敏感的应用。
L2CAP_CFG_TYPE_FCR0x04流控制和重传(FCR)启用 Enhanced Retransmission Mode 或 Streaming Mode。控制可靠性和拥塞管理。用于高可靠性场景。
L2CAP_CFG_TYPE_FCS0x05帧校验序列(FCS)用于启用/禁用帧校验(Frame Check Sequence),提升数据完整性。常用于对稳定性要求高的连接。
L2CAP_CFG_TYPE_EXT_FLOW0x06扩展流控(Extended Flow Spec)指定更详细的流控参数,如服务类别、最大延迟、最小带宽等。适用于复杂流控策略或定制化传输需求。
应用举例:
  1. A2DP(音频流) 通常会协商 MTU 和 Flush Timeout;
  2. HID(蓝牙键鼠) 可能会协商 QoS;
  3. RFCOMM 使用 FCR 来增强重传机制以提供可靠串口仿真;
  4. 文件传输(如 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_OKL2CEVT_L2CAP_CONNECT_RSP连接成功,进入配置阶段
L2CAP_CONN_PENDINGL2CEVT_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值