【Bluedroid】蓝牙启动之sdp_init 源码解析
本文围绕Android蓝牙协议栈中 SDP(Service Discovery Protocol,服务发现协议)模块的初始化函数sdp_init
展开,结合核心控制块tSDP_CB
及关联数据结构(如tL2CAP_CFG_INFO
、tCONN_CB
等)的定义与协作逻辑,详细解析 SDP 模块初始化的关键步骤,包括控制块内存初始化、连接定时器创建、L2CAP 通信参数配置、回调函数注册及服务注册流程。同时,阐明各数据结构如何支撑 SDP 模块的状态管理、连接控制与协议交互,为理解蓝牙服务发现的底层机制提供技术视角。
一、概述
1.1 SDP 模块的核心地位
SDP是蓝牙协议栈的核心组件,负责设备间服务的发现与信息交换(如查询耳机支持的音频服务、获取打印机的文件传输协议)。其核心功能依赖初始化阶段的资源准备与协议绑定,sdp_init
函数是 SDP 模块启动的入口,承担模块运行环境的初始化任务。
1.2 sdp_init
的关键初始化步骤
-
控制块内存初始化:通过
memset
清零全局控制块tSDP_CB sdp_cb
,确保所有运行时状态(如连接信息、配置参数)从初始状态开始。 -
连接定时器创建:为每个并发连接(由
SDP_MAX_CONNECTIONS
定义)分配定时器sdp_conn_timer
,用于监控连接超时,保障连接生命周期管理。 -
L2CAP 通信参数配置:设置
sdp_cb.l2cap_my_cfg
的 MTU(最大传输单元)为SDP_MTU_SIZE
,明确与底层 L2CAP 协议协商的通信能力(如单次传输的最大数据量)。 -
L2CAP 事件回调注册:为
sdp_cb.reg_info
绑定sdp_connect_ind
、sdp_data_ind
等回调函数,定义 L2CAP 连接请求、数据到达、断开等事件的处理逻辑,构建 SDP 与 L2CAP 的交互桥梁。 -
向 L2CAP 注册 SDP 服务:调用
L2CA_Register2
向 L2CAP 注册 SDP 服务(PSM 为BT_PSM_SDP
),完成协议栈底层的服务绑定,确保 SDP 能接收 L2CAP 传递的事件与数据。
1.3 核心数据结构的协作机制
SDP 模块的高效运行依赖多个数据结构的协同:
-
tSDP_CB
:全局控制块,整合 L2CAP 配置(l2cap_my_cfg
)、连接状态(ccb
数组)、本地服务数据库(server_db
)等核心信息,是模块运行的 “中枢”。 -
tL2CAP_CFG_INFO
:L2CAP 配置参数结构体,通过可选参数标记(如mtu_present
、qos_present
)灵活协商 MTU、QoS 等通信能力,适配不同业务场景(如实时音频的低延迟需求与文件传输的可靠性需求)。 -
tCONN_CB
:连接控制块,管理单个 SDP 连接的状态(con_state
)、定时器(sdp_conn_timer
)、数据缓存(rsp_list
)及发现结果(handles
数组),支撑多连接并发处理。 -
tL2CAP_APPL_INFO
:L2CAP 回调注册结构体,绑定pL2CA_DataInd_Cb
等事件处理函数,实现 SDP 对 L2CAP 事件的响应(如数据到达时解析 SDP 请求)。
二、源码分析
sdp_init
packages/modules/Bluetooth/system/stack/sdp/sdp_main.cc
/******************************************************************************/
/* G L O B A L S D P D A T A */
/******************************************************************************/
// SDP 模块的核心控制块(Control Block)结构体类型
// 用于存储 SDP 运行时的状态、连接信息、配置参数等关键数据
tSDP_CB sdp_cb;/* The maximum number of simultaneous client and server connections. */
#ifndef SDP_MAX_CONNECTIONS
#define SDP_MAX_CONNECTIONS 4 // 表示 SDP 支持的最大并发连接数
#endif/********************************************************************************* Function sdp_init** Description This function initializes the SDP unit.** Returns void*******************************************************************************/
void sdp_init(void) {/* Clears all structures and local SDP database (if Server is enabled) */memset(&sdp_cb, 0, sizeof(tSDP_CB));// 初始化连接定时器for (int i = 0; i < SDP_MAX_CONNECTIONS; i++) {sdp_cb.ccb[i].sdp_conn_timer = alarm_new("sdp.sdp_conn_timer");}// 配置 L2CAP MTU/* Initialize the L2CAP configuration. We only care about MTU */sdp_cb.l2cap_my_cfg.mtu_present = true; // 启用 MTU(最大传输单元)配置sdp_cb.l2cap_my_cfg.mtu = SDP_MTU_SIZE;// 设置 SDP 搜索参数sdp_cb.max_attr_list_size = SDP_MTU_SIZE - 16;sdp_cb.max_recs_per_search = SDP_MAX_DISC_SERVER_RECS;// 注册 L2CAP 回调函数sdp_cb.reg_info.pL2CA_ConnectInd_Cb = sdp_connect_ind; // 处理 L2CAP 连接请求指示(当其他设备请求与本地 SDP 建立连接时触发)sdp_cb.reg_info.pL2CA_ConnectCfm_Cb = sdp_connect_cfm;sdp_cb.reg_info.pL2CA_ConfigInd_Cb = sdp_config_ind;sdp_cb.reg_info.pL2CA_ConfigCfm_Cb = sdp_config_cfm;sdp_cb.reg_info.pL2CA_DisconnectInd_Cb = sdp_disconnect_ind; // 处理 L2CAP 断开连接指示(当连接异常断开时触发)sdp_cb.reg_info.pL2CA_DisconnectCfm_Cb = sdp_disconnect_cfm;sdp_cb.reg_info.pL2CA_DataInd_Cb = sdp_data_ind; // 处理 L2CAP 数据到达指示(当收到对端通过 L2CAP 发送的 SDP 数据时触发)sdp_cb.reg_info.pL2CA_Error_Cb = sdp_on_l2cap_error;// 向 L2CAP 注册 SDP 服务/* Now, register with L2CAP */if (!L2CA_Register2(BT_PSM_SDP, sdp_cb.reg_info, true /* enable_snoop */,nullptr, SDP_MTU_SIZE, 0, BTM_SEC_NONE)) {log::error("SDP Registration failed");}
}
蓝牙协议栈中 SDP(服务发现协议,Service Discovery Protocol)模块的初始化函数,主要完成:
-
控制块内存初始化;
-
连接定时器创建;
-
L2CAP 通信参数配置;
-
L2CAP 事件回调注册;
-
向 L2CAP 注册 SDP 服务,建立与底层协议的交互通道。
tSDP_CB sdp_cb
/* The main SDP control block */
typedef struct {// 存储 SDP 模块向 L2CAP声明的本地配置参数,用于协商与对端设备的通信能力tL2CAP_CFG_INFO l2cap_my_cfg; /* My L2CAP config */// 管理 SDP 的并发连接状态,每个元素对应一个与对端设备的 SDP 连接tCONN_CB ccb[SDP_MAX_CONNECTIONS];// 存储本地设备的服务记录数据库tSDP_DB server_db;// 存储 SDP 模块向 L2CAP 注册的事件回调函数,是 SDP 与 L2CAP 交互的桥梁tL2CAP_APPL_INFO reg_info; /* L2CAP Registration info */// 限制 SDP 搜索或查询操作中,单个属性列表的最大长度(单位:字节)uint16_t max_attr_list_size; /* Max attribute list size to use */// 限制 SDP 搜索操作中,每次返回的最大服务记录数(单位:条)uint16_t max_recs_per_search; /* Max records we want per seaarch */
} tSDP_CB;
tSDP_CB
用于存储 SDP 运行时的关键状态、配置参数和交互信息。它是 SDP 模块与底层 L2CAP 协议、上层应用以及内部连接管理的枢纽。
整合了 SDP 模块运行所需的核心信息:
-
与 L2CAP 的交互配置(
l2cap_my_cfg
、reg_info
); -
连接状态管理(
ccb
数组); -
本地服务数据存储(
server_db
); -
搜索参数限制(
max_attr_list_size
、max_recs_per_search
)。
通过这个控制块,SDP 模块能够高效处理服务发现请求、管理并发连接,并与蓝牙协议栈的底层(L2CAP)和上层(应用)协同工作。
tL2CAP_CFG_INFO
packages/modules/Bluetooth/system/stack/include/l2c_api.h
/* Define a structure to hold the configuration parameters. Since the* parameters are optional, for each parameter there is a boolean to* use to signify its presence or absence.*/
typedef struct {uint16_t result; /* Only used in confirm messages */bool mtu_present;uint16_t mtu;bool qos_present;FLOW_SPEC qos;bool flush_to_present; // 标记是否启用刷新超时配置uint16_t flush_to; // 超时时间bool fcr_present; // 标记是否启用 FCR 协商tL2CAP_FCR_OPTS fcr; // 标记是否启用 FCS 配置bool fcs_present; /* Optionally bypasses FCS checks */uint8_t fcs; /* '0' if desire is to bypass FCS, otherwise '1' */bool ext_flow_spec_present; // 标记是否启用扩展流量规格tHCI_EXT_FLOW_SPEC ext_flow_spec;uint16_t flags; /* bit 0: 0-no continuation, 1-continuation */
} tL2CAP_CFG_INFO;
L2CAP模块的配置参数结构体,用于在 L2CAP 连接建立或配置阶段协商双方的通信能力。通过可选参数标记(_present
布尔字段)灵活支持不同场景的配置需求,避免传输冗余数据。
-
对于简单业务(如普通数据传输),仅需协商 MTU 即可;
-
对于复杂业务(如实时音视频),可进一步协商 QoS、FCR 等参数,保障传输质量。
FLOW_SPEC
packages/modules/Bluetooth/system/stack/include/l2c_api.h
typedef struct {// QoS 控制标志位,用于标记 QoS 参数的扩展选项或特定行为uint8_t qos_flags; /* TBD */// 定义流量的服务类型,指示数据传输的优先级或服务等级uint8_t service_type; /* see below */// 定义令牌桶算法中令牌生成的平均速率(单位:字节 / 秒),用于控制长期平均数据传输速率uint32_t token_rate; /* bytes/second */// 定义令牌桶的最大容量(单位:字节),表示允许的最大突发数据量uint32_t token_bucket_size; /* bytes */// 定义允许的最大瞬时数据传输速率(单位:字节 / 秒),限制短时间内的流量峰值uint32_t peak_bandwidth; /* bytes/second */// 定义数据从发送端到接收端的最大允许延迟(单位:微秒),适用于实时业务(如语音通话)uint32_t latency; /* microseconds */// 定义延迟的最大波动范围(单位:微秒),即相邻数据包的延迟差异uint32_t delay_variation; /* microseconds */
} FLOW_SPEC;
L2CAP模块用于定义 QoS(服务质量)参数的结构体,主要在 L2CAP 连接建立或配置阶段协商双方的流量特性,确保数据传输满足业务的性能需求(如带宽、延迟、抖动等)。这是蓝牙支持多业务(如音频、视频、传感器数据)共存的核心机制之一。
tL2CAP_FCR_OPTS
packages/modules/Bluetooth/system/stack/include/l2c_api.h
typedef struct {
#define L2CAP_FCR_BASIC_MODE 0x00 // 基本模式
#define L2CAP_FCR_ERTM_MODE 0x03 // 增强重传模式(Enhanced Retransmission Mode)
#define L2CAP_FCR_LE_COC_MODE 0x05 // LE 连接导向通道模式(LE Connection-Oriented Channels)uint8_t mode;uint8_t tx_win_sz; // 定义发送端在未收到确认(ACK)前可连续发送的最大数据包数量(滑动窗口大小)uint8_t max_transmit; // 当数据包未收到 ACK 时,允许的最大重传次数。超过此次数后,链路将断开uint16_t rtrans_tout; // 发送端在未收到 ACK 时,等待重传的超时时间(单位:毫秒)。超时后触发重传uint16_t mon_tout; // 接收端在未收到任何数据包(包括数据或控制帧)时,判定链路失效的超时时间(单位:毫秒)uint16_t mps; // 定义单个 L2CAP 协议数据单元(PDU)的最大字节数。超过此大小的数据将被分片传输
} tL2CAP_FCR_OPTS;
配置 流量控制与重传(Flow Control and Retransmission, FCR)参数。FCR 机制是 L2CAP 的关键功能之一,通过配置不同的传输模式(如基本模式、增强重传模式)和参数(如窗口大小、超时时间),实现对数据传输可靠性与效率的平衡。
不同传输模式下,部分参数的有效性不同:
模式 | tx_win_sz | max_transmit | rtrans_tout(Retransmission Timeout) | mon_tout(Monitoring Timeout) | mps(Maximum Protocol Data Unit Size) |
基本模式(0x00) | 无意义 | 无意义 | 无意义 | 无意义 | 有效 |
增强重传模式(0x03) | 有效 | 有效 | 有效 | 有效 | 有效 |
LE COC 模式(0x05) | 有效 | 无意义 | 无意义 | 有效 | 有效 |
实际应用场景
-
BLE 传感器数据传输:使用 LE COC 模式(
L2CAP_FCR_LE_COC_MODE
),配置较小的mps
(如20
字节)和较长的mon_tout
(如1000ms
),平衡低功耗与链路可靠性。 -
蓝牙文件传输:使用 ERTM 模式(
L2CAP_FCR_ERTM_MODE
),增大tx_win_sz
(如4
)和max_transmit
(如3
),并调整rtrans_tout
匹配链路 RTT,提升吞吐量和可靠性。 -
实时音频流:使用基本模式(
L2CAP_FCR_BASIC_MODE
),忽略重传参数,减少协议开销,确保低延迟。
模式说明:
基本模式(
L2CAP_FCR_BASIC_MODE
):无流量控制和重传机制,数据按 “尽力而为” 方式传输。适用于对延迟敏感但可靠性要求低的场景(如实时音频流)。增强重传模式(
L2CAP_FCR_ERTM_MODE
):支持流量控制(滑动窗口)和自动重传(ARQ),确保数据可靠传输。适用于对可靠性要求高的场景(如文件传输、命令控制)。LE 连接导向通道模式(
L2CAP_FCR_LE_COC_MODE
):专为低功耗蓝牙(BLE)设计的连接导向模式,优化了低功耗场景下的周期性数据传输(如传感器数据上报)。
tHCI_EXT_FLOW_SPEC
packages/modules/Bluetooth/system/stack/include/hcidefs.h
/* Define the extended flow specification fields used by AMP */
typedef struct {uint8_t id;uint8_t stype; // 指示流量的服务类型,定义数据传输的优先级和 QoS 要求uint16_t max_sdu_size; // 定义上层协议(如 L2CAP、GATT)传递给 HCI 层的单个服务数据单元(SDU)的最大字节数uint32_t sdu_inter_time; // 指示连续两个 SDU 之间的最小时间间隔(单位:微秒,μs),用于控制数据发送速率uint32_t access_latency; // 定义数据从上层提交到 HCI 层,到控制器实际开始传输该数据的最大允许时间(单位:微秒)uint32_t flush_timeout; // 定义数据在控制器发送缓冲区中未被传输的最大时间(单位:微秒)。超时后,控制器将丢弃该数据以释放缓冲区
} tHCI_EXT_FLOW_SPEC;
HCI层定义的扩展流量规范结构体,主要用于 AMP(Alternate MAC/PHY)场景下的流量管理。AMP 允许蓝牙设备通过不同的 MAC/PHY 层(如经典蓝牙 BR/EDR、低功耗蓝牙 LE)传输数据,而该结构体用于配置流量的服务质量(QoS)参数,确保数据传输的可靠性、延迟和带宽符合业务需求。
当使用 AMP 技术时,需要为不同的数据流(如音频、视频、传感器数据)定义具体的流量参数,以便控制器分配资源(如带宽、缓冲区)并优化传输策略。
tL2CAP_CFG_INFO
/* Define a structure to hold the configuration parameters. Since the* parameters are optional, for each parameter there is a boolean to* use to signify its presence or absence.*/
typedef struct {uint16_t result; /* Only used in confirm messages */bool mtu_present; // 标识是否协商 L2CAP 最大传输单元(MTU)uint16_t mtu;bool qos_present; // 标识是否协商服务质量(QoS)参数FLOW_SPEC qos;bool flush_to_present; // 标识是否协商刷新超时(Flush Timeout)uint16_t flush_to; // 表示数据在链路缓冲区中未被传输的最大时间(单位:基础时间单位,通常为 1.25ms)bool fcr_present; // 标识是否协商流量控制与重传(FCR)策略tL2CAP_FCR_OPTS fcr;bool fcs_present; /* Optionally bypasses FCS checks */uint8_t fcs; /* '0' if desire is to bypass FCS, otherwise '1' */bool ext_flow_spec_present; // 标识是否协商扩展流量规范(仅在 AMP,Alternate MAC/PHY 场景下有效)tHCI_EXT_FLOW_SPEC ext_flow_spec;uint16_t flags; /* bit 0: 0-no continuation, 1-continuation */
} tL2CAP_CFG_INFO;
L2CAP层的配置参数结构体,用于在 L2CAP 链路建立或更新时协商可选的配置参数。L2CAP 链路建立时,两端设备需要协商一系列参数(如最大传输单元、流量控制策略、服务质量等)以优化数据传输。由于不同设备支持的特性可能不同(例如,经典蓝牙与低功耗蓝牙的能力差异),L2CAP 协议允许协商可选参数,tL2CAP_CFG_INFO
即为协议栈内部用于存储这些可选参数的容器。
tCONN_CB
packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* The maximum number of record handles retrieved in a search. */
#ifndef SDP_MAX_DISC_SERVER_RECS
#define SDP_MAX_DISC_SERVER_RECS 21
#endif/* Define the SDP Connection Control Block */
struct tCONN_CB {uint8_t con_state;uint8_t con_flags; // 连接标志位(如是否加密、是否认证,或自定义状态标志)RawAddress device_address;alarm_t* sdp_conn_timer;uint16_t rem_mtu_size;uint16_t connection_id;uint16_t list_len; /* length of the response in the GKI buffer */uint16_t pse_dynamic_attributes_len; /* length of the attributes need to beadded in final sdp response len */uint8_t* rsp_list; /* pointer to GKI buffer holding response */tSDP_DISCOVERY_DB* p_db; /* Database to save info into */tSDP_DISC_CMPL_CB* p_cb; /* Callback for discovery done */tSDP_DISC_CMPL_CB2*p_cb2; /* Callback for discovery done piggy back with the user data */const void* user_data; /* piggy back user data */uint32_thandles[SDP_MAX_DISC_SERVER_RECS]; /* Discovered server record handles */uint16_t num_handles; /* Number of server handles */uint16_t cur_handle; /* Current handle being processed */uint16_t transaction_id;uint16_t disconnect_reason; /* Disconnect reason */uint8_t disc_state; // 服务发现过程的状态bool is_attr_search; // 是否为属性搜索uint16_t cont_offset; /* Continuation state data in the server response */tSDP_CONT_INFO cont_info; /* structure to hold continuation information forthe server response */tCONN_CB() = default;private:tCONN_CB(const tCONN_CB&) = delete;
};
struct tCONN_CB
是蓝牙 SDP模块中的连接控制块(Connection Control Block),用于管理 SDP 连接的全生命周期(建立、数据传输、断开),并存储连接过程中的状态、数据和回调信息。
典型工作流程
假设设备 A 发起 SDP 服务发现请求,tCONN_CB
的关键交互如下:
①连接建立:
-
con_state
设为SDP_CONN_OPENING
,启动sdp_conn_timer
监控连接超时。 -
与设备 B 协商
rem_mtu_size
,确定单次传输的最大数据量。
②服务搜索:
-
设备 B 返回服务记录句柄列表,存储到
handles[]
,num_handles
记录实际数量。 -
若句柄数量超过
SDP_MAX_DISC_SERVER_RECS
,通过cont_offset
和cont_info
分段传输。
③属性获取:is_attr_search
设为 true
,逐个处理 cur_handle
对应的服务属性,数据缓存到 rsp_list
。
④发现完成:所有句柄处理完成后,调用 p_cb
或 p_cb2
通知上层,传递 p_db
中的服务发现结果。
⑤连接断开:con_state
设为 SDP_CONN_CLOSING
,记录 disconnect_reason
(如正常断开或超时),释放 sdp_conn_timer
和 rsp_list
缓冲区。
tSDP_DISCOVERY_DB
packages/modules/Bluetooth/system/stack/sdp/sdp_discovery_db.h
// 表示一条具体的服务记录(Service Record),用于存储从对端设备发现的服务的详细信息(如服务类型、支持的协议、属性列表等)
typedef struct t_sdp_disc_rec {tSDP_DISC_ATTR* p_first_attr; /* First attribute of record */struct t_sdp_disc_rec* p_next_rec; /* Addr of next linked record */uint32_t time_read; /* The time the record was read */RawAddress remote_bd_addr; /* Remote BD address */
} tSDP_DISC_REC;// 管理所有发现的服务记录(tSDP_DISC_REC),并支持内存管理、过滤条件配置和原始数据缓存
typedef struct {uint32_t mem_size; /* Memory size of the DB */uint32_t mem_free; /* Memory still available */tSDP_DISC_REC* p_first_rec; /* Addr of first record in DB */uint16_t num_uuid_filters; /* Number of UUIds to filter */bluetooth::Uuid uuid_filters[SDP_MAX_UUID_FILTERS]; /* UUIDs to filter */uint16_t num_attr_filters; /* Number of attribute filters */uint16_t attr_filters[SDP_MAX_ATTR_FILTERS]; /* Attributes to filter */uint8_t* p_free_mem; /* Pointer to free memory */uint8_t*raw_data; /* Received record from server. allocated/released by client */uint32_t raw_size; /* size of raw_data */uint32_t raw_used; /* length of raw_data used */
} tSDP_DISCOVERY_DB;
-
tSDP_DISC_REC
作为单个服务记录的载体,存储服务的核心属性和元数据; -
tSDP_DISCOVERY_DB
作为数据库,管理所有记录,支持过滤、检索和内存优化。
典型工作流程
假设手机(主机)通过 SDP 发现耳机(从机)的服务,tSDP_DISCOVERY_DB
的工作流程如下:
-
初始化数据库: 手机协议栈初始化
tSDP_DISCOVERY_DB
,分配内存(如mem_size = 4096
字节),设置p_free_mem
指向内存起始地址,mem_free = 4096
。 -
配置过滤条件: 手机上层应用请求发现 “音频服务”,协议栈配置
uuid_filters
为音频服务 UUID(如0x110A
),num_uuid_filters = 1
;同时配置attr_filters
为服务名称属性(0x0100
)和协议描述符列表(0x0004
),num_attr_filters = 2
。 -
接收并存储服务记录
耳机返回 SDP 响应,包含一条音频服务记录:
-
原始数据(如
raw_data
存储二进制格式的服务记录); -
解析后生成
tSDP_DISC_REC
结构体,通过p_first_rec
加入数据库链表; -
扣除已使用的内存(
mem_free -= sizeof(tSDP_DISC_REC) + 属性列表大小
)。
-
检索服务: 上层应用查询音频服务时,协议栈遍历
p_first_rec
链表,检查remote_bd_addr
(匹配耳机地址)和uuid_filters
(匹配音频 UUID),返回符合条件的tSDP_DISC_REC
及其p_first_attr
指向的属性列表(如服务名称 “Headphones”)。 -
内存回收与过期: 当
mem_free
不足时,协议栈根据time_read
淘汰旧记录(如超过 30 分钟未更新的记录),释放内存并更新p_free_mem
和mem_free
。
tSDP_DISC_CMPL_CB
packages/modules/Bluetooth/system/stack/include/sdp_callback.h
/* Define a callback function for when discovery is complete. */
// 基础回调接口,用于上层应用接收服务发现的最终结果(设备地址 + 状态码)
typedef void(tSDP_DISC_CMPL_CB)(const RawAddress& bd_addr, tSDP_RESULT result);
// 扩展回调接口,支持传递用户自定义数据,解决基础接口无法关联上下文的问题(例如,上层同时发起多个发现请求时,通过 user_data 区分不同请求的结果)
typedef void(tSDP_DISC_CMPL_CB2)(const RawAddress& bd_addr, tSDP_RESULT result,const void* user_data);packages/modules/Bluetooth/system/stack/include/sdp_status.h
/* Success code and error codes */
typedef enum : uint16_t {SDP_SUCCESS = 0x0000,SDP_INVALID_VERSION = 0x0001,SDP_INVALID_SERV_REC_HDL = 0x0002,SDP_INVALID_REQ_SYNTAX = 0x0003,SDP_INVALID_PDU_SIZE = 0x0004,SDP_INVALID_CONT_STATE = 0x0005,SDP_NO_RESOURCES = 0x0006,SDP_DI_REG_FAILED = 0x0007,SDP_DI_DISC_FAILED = 0x0008,SDP_NO_DI_RECORD_FOUND = 0x0009,SDP_ERR_ATTR_NOT_PRESENT = 0x000A,SDP_ILLEGAL_PARAMETER = 0x000B,HID_SDP_NO_SERV_UUID = (SDP_ILLEGAL_PARAMETER + 1),HID_SDP_MANDATORY_MISSING,SDP_NO_RECS_MATCH = 0xFFF0,SDP_CONN_FAILED = 0xFFF1,SDP_CFG_FAILED = 0xFFF2,SDP_GENERIC_ERROR = 0xFFF3,SDP_DB_FULL = 0xFFF4,SDP_CANCEL = 0xFFF8,
} tSDP_STATUS;
using tSDP_RESULT = tSDP_STATUS;
回调与状态码的协同工作
SDP 模块的典型流程中,回调与状态码的交互如下:
①上层应用通过 sdp_start_discovery()
发起服务发现,注册回调函数(如 tSDP_DISC_CMPL_CB2
)并传入 user_data
(如请求 ID)。
②SDP 模块与对端设备交互,执行发现操作(搜索服务记录、获取属性)。
③若发现完成(成功或失败),SDP 模块调用注册的回调函数,传递:
-
对端设备地址(
bd_addr
); -
最终状态(
result
,如SDP_SUCCESS
或SDP_CONN_FAILED
); -
用户自定义数据(
user_data
,如请求 ID)。
④上层应用根据 result
状态码处理结果(如显示服务列表或提示连接失败)。
tSDP_CONT_INFO
packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* Continuation information for the SDP server response */
typedef struct {uint16_t next_attr_index; /* attr index for next continuation response */uint16_t next_attr_start_id; /* attr id to start with for the attr index innext cont. response */const tSDP_RECORD* prev_sdp_rec; /* last sdp record that was completely sentin the response */bool last_attr_seq_desc_sent; /* whether attr seq length has been sentpreviously */uint16_t attr_offset; /* offset within the attr to keep trak of partialattributes in the responses */
} tSDP_CONT_INFO;
SDP 协议中,当服务记录(Service Record)或属性列表(Attribute List)的大小超过单次传输的最大 PDU(协议数据单元)长度(由 MTU 决定)时,服务器会将响应分段传输。tSDP_CONT_INFO
用于存储分段传输的上下文信息(如当前处理的属性位置、已发送的记录标识),确保:
-
接收方能正确拼接多段响应,避免数据丢失或乱序;
-
服务器能准确续传剩余数据,减少重复传输;
-
协议实现兼容不同设备的 MTU 限制(如经典蓝牙与低功耗蓝牙的 MTU 差异)。
tSDP_DB
packages/modules/Bluetooth/system/stack/sdp/sdpint.h
/* Define the SDP database */
typedef struct {uint32_t// 设备标识主记录句柄di_primary_handle; /* Device ID Primary record or NULL if nonexistent */uint16_t num_records;tSDP_RECORD record[SDP_MAX_RECORDS]; // 存储本地设备发布的所有服务记录
} tSDP_DB;
在蓝牙通信中,支持 SDP 协议的设备通常扮演两种角色:
-
SDP 客户端:主动查询对端设备的服务(如手机搜索耳机的音频服务);
-
SDP 服务器:被动响应查询,提供本地服务记录(如耳机发布自身支持的音频服务)。
struct tSDP_DB
即为 SDP 服务器的 “服务仓库”,存储本地设备所有可被发现的服务记录,是设备对外提供服务发现能力的基础。
Device ID 记录:是 SDP 中一类特殊的服务记录,用于描述设备的基本信息(如设备类型、制造商、型号),遵循蓝牙 SIG 定义的 Device ID 配置文件(Device ID Profile)。例如,耳机的 Device ID 记录可能包含制造商 ID(如
0x004C
对应 Apple)和产品型号。
tSDP_RECORD
packages/modules/Bluetooth/system/stack/sdp/sdpint.h
// 表示 SDP 服务记录中的单个属性(Attribute),用于描述服务的具体特征(如服务名称、支持的协议、端口号等)。SDP 协议通过属性实现对服务的精细化描述,而 tSDP_ATTRIBUTE 是这一描述的最小存储单元
/* Define the attribute element of the SDP database record */
typedef struct {uint32_t len; /* Number of bytes in the entry */uint8_t* value_ptr; /* Points to attr_pad */uint16_t id;uint8_t type; // 属性值的数据类型(由 SDP 规范定义)
} tSDP_ATTRIBUTE;// 表示一条完整的服务记录(Service Record),是 SDP 服务发现的核心实体。每条记录对应一个具体的服务(如音频传输服务、文件传输服务),由唯一的句柄标识,并包含多个属性(tSDP_ATTRIBUTE)
/* An SDP record consists of a handle, and 1 or more attributes */
typedef struct {uint32_t record_handle; // 服务记录句柄(由 SDP 服务器分配的唯一标识符),用于快速定位记录uint32_t free_pad_ptr; // attr_pad 数组中当前空闲位置的偏移量(字节)。用于内联存储属性值时的动态分配(例如,第一个属性使用 attr_pad[0~31],则 free_pad_ptr 设为 32)uint16_t num_attributes; // 当前记录包含的属性数量tSDP_ATTRIBUTE attribute[SDP_MAX_REC_ATTR];uint8_t attr_pad[SDP_MAX_PAD_LEN]; // 内联存储属性值的缓冲区(预分配的连续内存)。仅用于存储长度 ≤ SDP_MAX_PAD_LEN 的属性值
} tSDP_RECORD;
数据库的典型操作
-
添加服务记录:设备启动时,将预定义的服务(如音频服务、HID 服务)封装为
tSDP_RECORD
,并添加到record
数组,num_records
递增。 -
响应查询请求:当其他设备发起 SDP 查询(如搜索音频服务),SDP 服务器遍历
record
数组,匹配服务 UUID(通过tSDP_ATTRIBUTE
的id=0x0001
属性),返回符合条件的记录及其属性。 -
更新服务记录:若服务属性变化(如修改服务名称),找到对应的
tSDP_RECORD
,更新其attribute
数组中的相应tSDP_ATTRIBUTE
(可能需要调整free_pad_ptr
或重新分配value_ptr
)。
三级结构的协同关系:
tSDP_ATTRIBUTE
→ tSDP_RECORD
→ tSDP_DB
构成了 SDP 服务信息的三级存储体系:
-
属性层(
tSDP_ATTRIBUTE
):最小单元,存储服务的具体特征(如名称、协议)。 -
记录层(
tSDP_RECORD
):容器,将多个属性封装为一条完整的服务记录(如音频服务)。 -
数据库层(
tSDP_DB
):仓库,管理所有服务记录,支持设备对外发布服务。
服务记录的构建示例
假设构建一个 “音频传输服务” 的 tSDP_RECORD
:
tSDP_RECORD audio_record = {.record_handle = 0x00000001, // 记录句柄.num_attributes = 3, // 包含 3 个属性.attribute = {[0] = { // 服务类 UUID(属性 ID 0x0001).id = 0x0001,.type = 0x0A, // UUID 类型(128 位).len = 16, // 128 位 UUID 占 16 字节.value_ptr = &audio_record.attr_pad[0] // 内联存储到 attr_pad},[1] = { // 服务名称(属性 ID 0x0100).id = 0x0100,.type = 0x05, // 字符串类型.len = 12, // "Headphones" 占 12 字节.value_ptr = &audio_record.attr_pad[16] // 内联存储到 attr_pad},[2] = { // 协议描述符列表(属性 ID 0x0004).id = 0x0004,.type = 0x35, // 序列类型(包含多个协议描述符).len = 8, // 序列长度 8 字节.value_ptr = &audio_record.attr_pad[28] // 内联存储到 attr_pad}},.free_pad_ptr = 36 // attr_pad 已使用 36 字节(16+12+8)
};
// 内联存储的属性值直接写入 attr_pad
memcpy(audio_record.attr_pad, audio_service_uuid, 16); // 音频服务 UUID
memcpy(audio_record.attr_pad + 16, "Headphones", 12); // 服务名称
memcpy(audio_record.attr_pad + 28, protocol_descriptor, 8); // 协议描述符列表
tL2CAP_APPL_INFO
packages/modules/Bluetooth/system/stack/include/l2c_api.h
/* Connection indication callback prototype. Parameters are* BD Address of remote* Local CID assigned to the connection* PSM that the remote wants to connect to* Identifier that the remote sent*/
typedef void(tL2CA_CONNECT_IND_CB)(const RawAddress&, uint16_t, uint16_t,uint8_t);/* Connection confirmation callback prototype. Parameters are* Local CID* Result - 0 = connected* If there is an error, tL2CA_ERROR_CB is invoked*/
typedef void(tL2CA_CONNECT_CFM_CB)(uint16_t, uint16_t);/* Configuration indication callback prototype. Parameters are* Local CID assigned to the connection* Pointer to configuration info*/
typedef void(tL2CA_CONFIG_IND_CB)(uint16_t, tL2CAP_CFG_INFO*);/* Configuration confirm callback prototype. Parameters are* Local CID assigned to the connection* Initiator (1 for local, 0 for remote)* Initial config from remote* If there is an error, tL2CA_ERROR_CB is invoked*/
typedef void(tL2CA_CONFIG_CFM_CB)(uint16_t, uint16_t, tL2CAP_CFG_INFO*);/* Disconnect indication callback prototype. Parameters are* Local CID* Boolean whether upper layer should ack this*/
typedef void(tL2CA_DISCONNECT_IND_CB)(uint16_t, bool);/* Disconnect confirm callback prototype. Parameters are* Local CID* Result*/
typedef void(tL2CA_DISCONNECT_CFM_CB)(uint16_t, uint16_t);/* Disconnect confirm callback prototype. Parameters are* Local CID* Result*/
typedef void(tL2CA_DATA_IND_CB)(uint16_t, BT_HDR*);/* Congestion status callback protype. This callback is optional. If* an application tries to send data when the transmit queue is full,* the data will anyways be dropped. The parameter is:* Local CID* true if congested, false if uncongested*/
typedef void(tL2CA_CONGESTION_STATUS_CB)(uint16_t, bool);/* Transmit complete callback protype. This callback is optional. If* set, L2CAP will call it when packets are sent or flushed. If the* count is 0xFFFF, it means all packets are sent for that CID (eRTM* mode only). The parameters are:* Local CID* Number of SDUs sent or dropped*/
typedef void(tL2CA_TX_COMPLETE_CB)(uint16_t, uint16_t);/** Notify the user when the remote send error result on ConnectRsp or ConfigRsp* The parameters are:* Local CID* Error type (L2CAP_CONN_OTHER_ERROR for ConnectRsp,* L2CAP_CFG_FAILED_NO_REASON for ConfigRsp)*/
typedef void(tL2CA_ERROR_CB)(uint16_t, uint16_t);/* Create credit based connection request callback prototype. Parameters are* BD Address of remote* Vector of allocated local cids to accept* PSM* Peer MTU* Identifier that the remote sent*/
typedef void(tL2CA_CREDIT_BASED_CONNECT_IND_CB)(const RawAddress& bdaddr,std::vector<uint16_t>& lcids,uint16_t psm, uint16_t peer_mtu,uint8_t identifier);/* Define the structure that applications use to register with* L2CAP. This structure includes callback functions. All functions* MUST be provided, with the exception of the "connect pending"* callback and "congestion status" callback.*/
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; // 信用基连接指示回调(LE COC 场景)tL2CA_CREDIT_BASED_CONNECT_CFM_CB* pL2CA_CreditBasedConnectCfm_Cb; // 信用基连接确认回调(LE COC 场景)tL2CA_CREDIT_BASED_RECONFIG_COMPLETED_CB*pL2CA_CreditBasedReconfigCompleted_Cb; // 信用基重配置完成回调(LE COC 场景)tL2CA_CREDIT_BASED_COLLISION_IND_CB* pL2CA_CreditBasedCollisionInd_Cb; // 信用基冲突指示回调(LE COC 场景)
} tL2CAP_APPL_INFO;
L2CAP 的核心功能包括连接管理、配置协商、数据传输、断开处理等,每个功能对应一组回调函数,用于通知上层应用关键事件。上层应用通过注册这些回调,深度参与 L2CAP 链路的全生命周期管理(连接、配置、传输、断开)。
典型交互流程示例
以手机与耳机建立 L2CAP 音频链路为例,回调的触发顺序如下:
①连接建立
-
手机应用发起连接请求(指定 PSM 为音频服务)。
-
耳机 L2CAP 层收到请求,触发
pL2CA_ConnectInd_Cb
(连接指示回调),耳机应用检查 PSM 后接受。 -
手机 L2CAP 层完成连接,触发
pL2CA_ConnectCfm_Cb
(连接确认回调),手机应用确认连接成功。
②配置协商
-
手机 L2CAP 层发起 MTU 协商(如请求 MTU=512),触发
pL2CA_ConfigInd_Cb
(配置指示回调)。 -
耳机应用响应配置(支持 MTU=512),触发
pL2CA_ConfigCfm_Cb
(配置确认回调),双方确认 MTU。
③数据传输
-
耳机发送音频数据,手机 L2CAP 层触发
pL2CA_DataInd_Cb
(数据指示回调),手机应用提取音频数据播放。 -
若手机发送队列满,触发
pL2CA_CongestionStatus_Cb
(拥塞状态回调),手机应用暂停发送以避免丢包。
④断开连接
-
手机应用发起断开请求,耳机 L2CAP 层触发
pL2CA_DisconnectInd_Cb
(断开指示回调),耳机应用释放资源。 -
手机 L2CAP 层完成断开,触发
pL2CA_DisconnectCfm_Cb
(断开确认回调),手机应用确认链路关闭。
四、SDP 初始化流程图
五、SDP 服务发现时序图
六、总结
SDP 模块的初始化(sdp_init
)是蓝牙服务发现功能的基础,通过控制块初始化、定时器创建、L2CAP 参数配置及回调注册,为 SDP 模块的运行奠定环境基础。核心数据结构(如tSDP_CB
、tL2CAP_CFG_INFO
)通过分层设计(全局控制 - 连接管理 - 协议交互),实现了状态管理、连接控制与协议协同的高效整合。理解这些初始化细节与数据结构的协作逻辑,是掌握蓝牙服务发现底层机制、解决实际开发中连接异常(如超时、数据乱序)及性能优化(如 MTU 调优)问题的关键。