本文深入剖析Bluedroid蓝牙协议栈中 SDP(服务发现协议)服务记录的全生命周期管理流程,涵盖初始化、记录创建、服务搜索、记录删除等核心环节。通过解析代码逻辑与数据结构,揭示各模块间的协作机制,包括线程安全设计、回调机制及协议栈交互细节,为蓝牙服务开发与调试提供系统性参考。
一、概述
蓝牙SDP协议用于设备间服务发现,其实现分为四层:
-
接口层(btif):提供
btif_sdp_get_interface
接口,封装init
、search
、create_sdp_record
等操作。 -
业务逻辑层(BTA):通过
BTA_SdpSearch
、BTA_SdpCreateRecordByUser
派发任务到主线程。 -
协议栈层(Stack):实现SDP数据库管理(
SDP_CreateRecord
、SDP_AddAttribute
)。 -
资源管理层:通过槽位(
sdp_slots
)管理记录内存,支持动态分配与释放。
关键设计:
-
线程安全:使用
std::recursive_mutex
保护槽位操作。 -
内存优化:预计算记录大小实现紧凑存储。
-
异步模型:通过
do_in_main_thread
解耦调用与执行。
二、源码解读
2.1 SDP 模块初始化与架构设计
①接口封装:通过btif_sdp_get_interface
暴露统一接口结构体sdp_if
,包含初始化(init
)、反初始化(deinit
)、搜索(search
)、记录创建(create_sdp_record
)和删除(remove_sdp_record
)等核心功能,实现模块解耦。
②初始化流程
-
init
函数注册回调bt_sdp_callbacks
,调用sdp_server_init
初始化槽位(sdp_slots
),并通过btif_enable_service
启用 SDP 服务。 -
sdp_server_init
初始化固定大小的槽位数组(MAX_SDP_SLOTS=128
),标记所有槽位为空闲(SDP_RECORD_FREE
)。
③线程安全:关键操作(如槽位分配、释放)通过std::recursive_mutex
加锁,避免多线程竞争。
④deinit
:init的反向操作,释放槽位内存并禁用服务。
btif_sdp_get_interface
packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
// 使用初始化列表对 sdp_if 进行初始化
static const btsdp_interface_t sdp_if = {
sizeof(btsdp_interface_t), init, deinit, search, create_sdp_record,
remove_sdp_record};
// 返回定义的 SDP 接口结构体实例的指针。其他模块可以通过调用这个函数来获取 SDP 接口的访问权限
const btsdp_interface_t* btif_sdp_get_interface(void) {
log::verbose("");
return &sdp_if;
}
定义了一个 SDP 接口结构体实例,封装 SDP 接口的实现,并提供一个统一的接口供其他模块使用。通过 btif_sdp_get_interface
函数,其他模块可以获取到 SDP 接口的指针,从而调用接口中定义的各种功能函数,如初始化、搜索服务、创建和移除 SDP 记录等。
init
packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
static btsdp_callbacks_t* bt_sdp_callbacks = NULL;
static bt_status_t init(btsdp_callbacks_t* callbacks) {
log::verbose("Sdp Search Init");
bt_sdp_callbacks = callbacks;
sdp_server_init(); // 对 SDP 服务器进行初始化
btif_enable_service(BTA_SDP_SERVICE_ID); // 启用 SDP 服务
return BT_STATUS_SUCCESS;
}
对蓝牙 SDP(Service Discovery Protocol,服务发现协议)进行初始化操作。SDP 协议用于在蓝牙设备之间发现可用的服务及其相关信息。
sdp_server_init
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
bt_status_t sdp_server_init() {
log::verbose("Sdp Server Init");
init_sdp_slots();
return BT_STATUS_SUCCESS;
}
对蓝牙 SDP服务器进行初始化操作。调用 init_sdp_slots
函数执行具体的初始化工作,最后返回表示操作成功的状态码。
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
typedef union {
bluetooth_sdp_hdr_overlay hdr;
bluetooth_sdp_mas_record mas;
bluetooth_sdp_mns_record mns;
bluetooth_sdp_pse_record pse;
bluetooth_sdp_pce_record pce;
bluetooth_sdp_ops_record ops;
bluetooth_sdp_sap_record sap;
bluetooth_sdp_dip_record dip;
bluetooth_sdp_mps_record mps;
} bluetooth_sdp_record;
typedef struct {
sdp_state_t state;
int sdp_handle;
bluetooth_sdp_record* record_data;
} sdp_slot_t;
#define MAX_SDP_SLOTS 128
static sdp_slot_t sdp_slots[MAX_SDP_SLOTS];
static void init_sdp_slots() {
int i;
memset(sdp_slots, 0, sizeof(sdp_slot_t) * MAX_SDP_SLOTS);
/* if SDP_RECORD_FREE is zero - no need to set the value */
if (SDP_RECORD_FREE != 0) {
for (i = 0; i < MAX_SDP_SLOTS; i++) {
sdp_slots[i].state = SDP_RECORD_FREE;
}
}
}
对 SDP 槽位进行初始化,确保所有槽位在使用前都处于空闲状态。
btif_enable_service(BTA_SDP_SERVICE_ID)
packages/modules/Bluetooth/system/btif/src/btif_core.cc
typedef uint32_t tBTA_SERVICE_MASK;
static tBTA_SERVICE_MASK btif_enabled_services = 0;
/*******************************************************************************
*
* Function btif_enable_service
*
* Description Enables the service 'service_ID' to the service_mask.
* Upon BT enable, BTIF core shall invoke the BTA APIs to
* enable the profiles
*
******************************************************************************/
void btif_enable_service(tBTA_SERVICE_ID service_id) {
btif_enabled_services |= (1 << service_id); // 设置服务掩码,表示该服务已启用
log::verbose("current services:0x{:x}", btif_enabled_services);
if (btif_is_enabled()) {
btif_dm_enable_service(service_id, true); // 向蓝牙协议栈发送指令,实际启用该服务
}
}
启用 SDP 服务。
deinit
packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
static bt_status_t deinit() {
log::verbose("Sdp Search Deinit");
bt_sdp_callbacks = NULL;
sdp_server_cleanup();
btif_disable_service(BTA_SDP_SERVICE_ID);
return BT_STATUS_SUCCESS;
}
清理蓝牙 SDP相关资源并禁用 SDP 服务。包括清空回调函数指针、清理 SDP 服务器和禁用 SDP 服务等操作。
sdp_server_cleanup
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
void sdp_server_cleanup() {
log::verbose("Sdp Server Cleanup");
std::unique_lock<std::recursive_mutex> lock(sdp_lock);
int i;
for (i = 0; i < MAX_SDP_SLOTS; i++) {
/*remove_sdp_record(i); we cannot send messages to the other threads, since
* they might
* have been shut down already. Just do local cleanup.
*/
free_sdp_slot(i);
}
}
对蓝牙 SDP 服务器进行清理,确保服务器在关闭时释放所有占用的资源。通过加锁操作保证了清理过程的线程安全,避免了多线程环境下的资源竞争问题。同时,由于考虑到其他线程可能已经关闭,只进行本地清理操作。
free_sdp_slot
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
static int free_sdp_slot(int id) {
int handle = -1;
bluetooth_sdp_record* record = NULL;
if (id < 0 || id >= MAX_SDP_SLOTS) {
log::error("failed - id {} is invalid", id);
return handle;
}
{
std::unique_lock<std::recursive_mutex> lock(sdp_lock);
handle = sdp_slots[id].sdp_handle;
sdp_slots[id].sdp_handle = 0;
if (sdp_slots[id].state != SDP_RECORD_FREE) {
/* safe a copy of the pointer, and free after unlock() */
record = sdp_slots[id].record_data;
}
sdp_slots[id].state = SDP_RECORD_FREE;
}
if (record != NULL) {
osi_free(record);
} else {
// Record have already been freed
handle = -1;
}
return handle;
}
通过对传入的 ID 进行有效性检查、加锁确保线程安全、清理槽位信息和释放记录数据,实现了对指定 SDP 槽位的释放操作。在释放过程中,遵循了先保存数据再释放内存的原则,避免了在持有锁的情况下进行内存操作,提高了程序的并发性能和健壮性。
2.2 服务搜索流程
①异步搜索发起:search
函数调用BTA_SdpSearch
,通过do_in_main_thread
在主线程启动搜索:
-
检查当前 SDP 操作状态(
sdp_active
),避免并发搜索。 -
初始化发现数据库(
SDP_InitDiscoveryDb
),设置 UUID 和属性过滤器。 -
发起 L2CAP 连接(
sdp_conn_originate
),通过SDP_ServiceSearchAttributeRequest2
执行搜索请求。
②连接与安全策略
-
L2CA_ConnectReq2
设置安全级别(BTM_SetSecurityLevel
),确保连接符合协议规范(如加密需伴随认证)。 -
sdp_conn_originate
分配连接控制块(CCB),处理链路状态(已连接 / 断开中),确保连接请求可靠传递。
③结果回调:搜索完成后通过bta_sdp_search_cback
触发BTA_SDP_SEARCH_COMP_EVT
,sdp_dm_cback
调用btif_transfer_context
深拷贝结果数据,避免跨模块指针引用问题。
search
/packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
static bt_status_t search(RawAddress* bd_addr, const Uuid& uuid) {
BTA_SdpSearch(*bd_addr, uuid);
return BT_STATUS_SUCCESS;
}
BTA_SdpSearch
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_api.cc
/*******************************************************************************
*
* Function BTA_SdpSearch
*
* Description This function performs service discovery for a specific
* service on given peer device. When the operation is
* completed the tBTA_SDP_DM_CBACK callback function will be
* called with a BTA_SDP_SEARCH_COMPLETE_EVT.
*
* Returns BTA_SDP_SUCCESS, if the request is being processed.
* BTA_SDP_FAILURE, otherwise.
*
******************************************************************************/
tBTA_SDP_STATUS BTA_SdpSearch(const RawAddress& bd_addr,
const bluetooth::Uuid& uuid) {
do_in_main_thread(FROM_HERE, base::BindOnce(bta_sdp_search, bd_addr, uuid));
return BTA_SDP_SUCCESS;
}
在指定的蓝牙对等设备上执行特定服务的发现操作。启动一个异步的蓝牙 SDP 服务发现操作。将服务发现任务封装在一个一次性的任务中,并在主线程中异步执行。函数立即返回 BTA_SDP_SUCCESS
,表示请求正在处理,而实际的操作结果会通过回调函数通知调用者。提高了程序的响应性,避免阻塞主线程。
当服务发现操作完成后,会调用 tBTA_SDP_DM_CBACK
回调函数,并传递 BTA_SDP_SEARCH_COMPLETE_EVT
事件。该函数返回一个 tBTA_SDP_STATUS
类型的值,表示请求是否正在处理。
bta_sdp_search
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_act.cc
/*******************************************************************************
*
* Function bta_sdp_search
*
* Description Discovers all sdp records for an uuid on remote device
*
* Returns void
*
******************************************************************************/
void bta_sdp_search(const RawAddress bd_addr, const bluetooth::Uuid uuid) {
tBTA_SDP_STATUS status = BTA_SDP_FAILURE;
log::verbose("in, sdp_active:{}", bta_sdp_cb.sdp_active);
// 1. 检查 SDP 是否正在进行
if (bta_sdp_cb.sdp_active) { // 表示 SDP 操作正在进行
/* SDP is still in progress */
status = BTA_SDP_BUSY;
if (bta_sdp_cb.p_dm_cback) {
tBTA_SDP_SEARCH_COMP result;
memset(&result, 0, sizeof(result));
result.uuid = uuid;
result.remote_addr = bd_addr;
result.status = status;
tBTA_SDP bta_sdp;
bta_sdp.sdp_search_comp = result;
bta_sdp_cb.p_dm_cback(BTA_SDP_SEARCH_COMP_EVT, &bta_sdp, NULL); // 通知调用者SDP操作忙碌
}
return;
}
// 2. 启动 SDP 操作
bta_sdp_cb.sdp_active = true; // 表示 SDP 操作开始
bta_sdp_cb.remote_addr = bd_addr;
/* initialize the search for the uuid */
log::verbose("init discovery with UUID: {}", uuid.ToString());
get_legacy_stack_sdp_api()->service.SDP_InitDiscoveryDb(
p_bta_sdp_cfg->p_sdp_db, p_bta_sdp_cfg->sdp_db_size, 1, &uuid, 0, NULL); // 初始化 SDP 发现数据库
// 3. 启动 SDP 服务搜索请求
Uuid* bta_sdp_search_uuid = (Uuid*)osi_malloc(sizeof(Uuid));
*bta_sdp_search_uuid = uuid;
// 发起 SDP 服务搜索属性请求
if (!get_legacy_stack_sdp_api()->service.SDP_ServiceSearchAttributeRequest2(
bd_addr, p_bta_sdp_cfg->p_sdp_db, bta_sdp_search_cback,
(void*)bta_sdp_search_uuid)) {
bta_sdp_cb.sdp_active = false; // 表示 SDP 操作结束
/* failed to start SDP. report the failure right away */
if (bta_sdp_cb.p_dm_cback) {
tBTA_SDP_SEARCH_COMP result;
memset(&result, 0, sizeof(result));
result.uuid = uuid;
result.remote_addr = bd_addr;
result.status = status;
tBTA_SDP bta_sdp;
bta_sdp.sdp_search_comp = result;
bta_sdp_cb.p_dm_cback(BTA_SDP_SEARCH_COMP_EVT, &bta_sdp, NULL); // 通知调用者SDP操作失败
bluetooth::shim::CountCounterMetrics(
android::bluetooth::CodePathCounterKeyEnum::SDP_FAILURE, 1);
}
}
/*
else report the result when the cback is called
*/
}
在远程蓝牙设备上发现指定 UUID 对应的所有 SDP记录。通过检查 SDP 操作状态,避免了同时进行多个 SDP 操作。在 SDP 操作空闲时,会初始化 SDP 发现数据库并发起服务搜索请求。若请求失败,会立即通知回调函数;若请求成功,结果将在回调函数 bta_sdp_search_cback
被调用时处理。
SDP_InitDiscoveryDb
packages/modules/Bluetooth/system/stack/sdp/sdp_api.cc
/*******************************************************************************
*
* Function SDP_InitDiscoveryDb
*
* Description This function is called to initialize a discovery database.
*
* Parameters: p_db - (input) address of an area of memory where the
* discovery database is managed.
* len - (input) size (in bytes) of the memory
* NOTE: This must be larger than
* sizeof(tSDP_DISCOVERY_DB)
* num_uuid - (input) number of UUID filters applied
* p_uuid_list - (input) list of UUID filters
* num_attr - (input) number of attribute filters applied
* p_attr_list - (input) list of attribute filters
*
*
* Returns bool
* true if successful
* false if one or more parameters are bad
*
******************************************************************************/
bool SDP_InitDiscoveryDb(tSDP_DISCOVERY_DB* p_db, uint32_t len,
uint16_t num_uuid, const Uuid* p_uuid_list,
uint16_t num_attr, const uint16_t* p_attr_list) {
// 1. 参数验证
uint16_t xx;
/* verify the parameters */
if (p_db == NULL || (sizeof(tSDP_DISCOVERY_DB) > len) ||
num_attr > SDP_MAX_ATTR_FILTERS || num_uuid > SDP_MAX_UUID_FILTERS) {
log::error(
"SDP_InitDiscoveryDb Illegal param: p_db {}, len {}, num_uuid {}, "
"num_attr {}",
fmt::ptr(p_db), len, num_uuid, num_attr);
return (false);
}
// 2. 数据库内存初始化
memset(p_db, 0, (size_t)len);
p_db->mem_size = len - sizeof(tSDP_DISCOVERY_DB); // 计算数据库可用于存储记录的有效内存大小
p_db->mem_free = p_db->mem_size; // 初始化可用内存大小为总有效内存大小
p_db->p_first_rec = NULL;
p_db->p_free_mem = (uint8_t*)(p_db + 1); // 将指向可用内存起始位置的指针设置为p_db结构体之后的地址
// 3. UUID 过滤器设置
// 将 p_uuid_list中的 UUID 过滤器依次复制到 p_db->uuid_filters 数组中
for (xx = 0; xx < num_uuid; xx++) p_db->uuid_filters[xx] = *p_uuid_list++;
p_db->num_uuid_filters = num_uuid; // 记录实际使用的 UUID 过滤器数量
// 4. 属性过滤器设置及排序
for (xx = 0; xx < num_attr; xx++) p_db->attr_filters[xx] = *p_attr_list++;
/* sort attributes */
sdpu_sort_attr_list(num_attr, p_db); // 对属性过滤器列表进行排序,以便后续处理
p_db->num_attr_filters = num_attr; //记录实际使用的属性过滤器数量
return (true);
}
初始化一个用于蓝牙服务发现协议(SDP)的发现数据库。该数据库用于存储和管理在服务发现过程中检索到的信息。通过对传入参数的严格验证和对发现数据库的初始化设置,确保了数据库在使用前处于正确的状态。设置了数据库的内存信息、UUID 过滤器和属性过滤器,并对属性过滤器进行排序,为后续的服务发现操作提供了基础。
SDP_ServiceSearchAttributeRequest2
packages/modules/Bluetooth/system/stack/sdp/sdp_api.cc
/*******************************************************************************
*
* Function SDP_ServiceSearchAttributeRequest2
*
* Description This function queries an SDP server for information.
*
* The difference between this API function and the function
* SDP_ServiceSearchRequest is that this one does a
* combined ServiceSearchAttributeRequest SDP function.
* (This is for Unplug Testing)
*
* Returns true if discovery started, false if failed.
*
******************************************************************************/
bool SDP_ServiceSearchAttributeRequest2(const RawAddress& p_bd_addr,
tSDP_DISCOVERY_DB* p_db,
tSDP_DISC_CMPL_CB2* p_cb2,
const void* user_data) {
//1. 发起连接
tCONN_CB* p_ccb; // 表示连接控制块,用于管理与 SDP 服务器的连接
/* Specific BD address */
p_ccb = sdp_conn_originate(p_bd_addr); // 尝试发起与指定设备的 SDP 连接
if (!p_ccb) return (false);
//2. 配置连接控制块
p_ccb->disc_state = SDP_DISC_WAIT_CONN; // 表示当前正在等待与 SDP 服务器建立连接
p_ccb->p_db = p_db; // 将传入的发现数据库指针赋值给连接控制块,以便后续将服务发现结果存储到该数据库中
p_ccb->p_cb2 = p_cb2; // 将传入的回调函数指针赋值给连接控制块,以便在服务发现完成时调用该回调函数
p_ccb->is_attr_search = true; // 表示这是一个属性搜索请求
p_ccb->user_data = user_data; // 将用户自定义的数据指针赋值给连接控制块,以便在回调函数被调用时传递给它
return (true);
}
通过发起与目标蓝牙设备的 SDP 连接,并配置连接控制块的相关信息,为服务发现过程做好准备。若连接发起成功,则返回 true
表示发现过程已启动;若连接发起失败,则返回 false
。为后续的服务发现操作奠定了基础。
与 SDP_ServiceSearchRequest
函数的区别在于,此函数执行的是一个组合的 ServiceSearchAttributeRequest
SDP 功能,为了进行拔插测试(Unplug Testing)而设计。函数返回一个布尔值,若成功启动发现过程则返回 true
,若失败则返回 false
。
sdp_conn_originate
packages/modules/Bluetooth/system/stack/sdp/sdp_main.cc
/*******************************************************************************
*
* Function sdp_conn_originate
*
* Description This function is called from the API to originate a
* connection.
*
* Returns void
*
******************************************************************************/
tCONN_CB* sdp_conn_originate(const RawAddress& p_bd_addr) {
// 1. 变量声明
tCONN_CB* p_ccb; // 表示连接控制块,存储与连接相关的信息
uint16_t cid; // 存储 L2CAP 连接的连接 ID
// 2. 分配连接控制块
/* Allocate a new CCB. Return if none available. */
p_ccb = sdpu_allocate_ccb(); // 尝试分配一个新的连接控制块
if (p_ccb == NULL) {
log::warn("no spare CCB for peer {}", ADDRESS_TO_LOGGABLE_CSTR(p_bd_addr));
return (NULL);
}
log::verbose("SDP - Originate started for peer {}",
ADDRESS_TO_LOGGABLE_CSTR(p_bd_addr));
// 3. 检查是否已有活动的 SDP 连接
/* Look for any active sdp connection on the remote device */
cid = sdpu_get_active_ccb_cid(p_bd_addr);
// 4. 设置连接标志和保存设备地址
/* We are the originator of this connection */
p_ccb->con_flags |= SDP_FLAGS_IS_ORIG; // 表示当前设备是该连接的发起者
/* Save the BD Address */
p_ccb->device_address = p_bd_addr;
// 5. 根据情况发起 L2CAP 连接请求
/* Transition to the next appropriate state, waiting for connection confirm */
if (!bluetooth::common::init_flags::sdp_serialization_is_enabled() ||
cid == 0) {
p_ccb->con_state = SDP_STATE_CONN_SETUP; // 表示正在进行连接建立
cid = L2CA_ConnectReq2(BT_PSM_SDP, p_bd_addr, BTM_SEC_NONE); // 发起 L2CAP 连接请求
} else { // 已有活动的 SDP 连接
p_ccb->con_state = SDP_STATE_CONN_PEND; // 表示连接处于等待状态
log::warn("SDP already active for peer {}. cid={:#0x}",
ADDRESS_TO_LOGGABLE_CSTR(p_bd_addr), cid);
}
// 6. 检查 L2CAP 连接请求是否成功
/* Check if L2CAP started the connection process */
if (cid == 0) { // 表示 L2CAP 连接请求失败
log::warn("SDP - Originate failed for peer {}",
ADDRESS_TO_LOGGABLE_CSTR(p_bd_addr));
sdpu_release_ccb(*p_ccb); // 释放之前分配的连接控制块
return (NULL);
}
// 7. 返回连接控制块指针
p_ccb->connection_id = cid; // 将连接 ID 保存到连接控制块中
return (p_ccb);
}
发起一个到指定蓝牙设备的 SDP连接。从 API 层被调用,会尝试分配连接控制块(CCB),检查是否已有活动的 SDP 连接,然后根据情况发起 L2CAP(Logical Link Control and Adaptation Protocol,逻辑链路控制和适配协议)连接请求。如果连接发起成功,返回指向连接控制块的指针;若失败,则返回 NULL
。
L2CA_ConnectReq2
packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc
uint16_t L2CA_ConnectReq2(uint16_t psm, const RawAddress& p_bd_addr,
uint16_t sec_level) {
get_btm_client_interface().security.BTM_SetSecurityLevel(
true, "", 0, sec_level, psm, 0, 0);
return L2CA_ConnectReq(psm, p_bd_addr);
}
发起一个 L2CAP连接请求。在发起连接请求之前,先设置目标设备的安全级别,然后调用 L2CA_ConnectReq
函数来实际发起连接。
BTM_SetSecurityLevel
packages/modules/Bluetooth/system/stack/btm/btm_sec.cc
/*******************************************************************************
*
* Function BTM_SetSecurityLevel
*
* Description Register service security level with Security Manager
*
* Parameters: is_originator - true if originating the connection
* p_name - Name of the service relevant only if
* authorization will show this name to user.
* Ignored if BT_MAX_SERVICE_NAME_LEN is 0.
* service_id - service ID for the service passed to
* authorization callback
* sec_level - bit mask of the security features
* psm - L2CAP PSM
* mx_proto_id - protocol ID of multiplexing proto below
* mx_chan_id - channel ID of multiplexing proto below
*
* Returns true if registered OK, else false
*
******************************************************************************/
bool BTM_SetSecurityLevel(bool is_originator, const char* p_name,
uint8_t service_id, uint16_t sec_level, uint16_t psm,
uint32_t mx_proto_id, uint32_t mx_chan_id) {
return btm_sec_cb.AddService(is_originator, p_name, service_id, sec_level,
psm, mx_proto_id, mx_chan_id);
}
向蓝牙安全管理器(Security Manager)注册服务的安全级别。通过调用 btm_sec_cb
对象的 AddService
方法,将服务的相关信息(包括发起连接标志、服务名称、服务 ID、安全级别、L2CAP PSM 等)传递给安全管理器,以便在建立连接时应用相应的安全策略。
tBTM_SEC_CB::AddService
packages/modules/Bluetooth/system/stack/btm/btm_sec_cb.cc
#define BTM_NO_AVAIL_SEC_SERVICES ((uint16_t)0xffff)
bool tBTM_SEC_CB::AddService(bool is_originator, const char* p_name,
uint8_t service_id, uint16_t sec_level,
uint16_t psm, uint32_t mx_proto_id,
uint32_t mx_chan_id) {
tBTM_SEC_SERV_REC* p_srec;
uint16_t index;
uint16_t first_unused_record = BTM_NO_AVAIL_SEC_SERVICES;
bool record_allocated = false;
log::verbose("sec_level:0x{:x}", sec_level);
/* See if the record can be reused (same service name, psm, mx_proto_id,
service_id, and mx_chan_id), or obtain the next unused record */
p_srec = &sec_serv_rec[0];
// 1. 记录复用与分配(核心资源管理)
for (index = 0; index < BTM_SEC_MAX_SERVICE_RECORDS; index++, p_srec++) {
/* Check if there is already a record for this service */
if (p_srec->security_flags & BTM_SEC_IN_USE) {
// // 检查是否存在相同服务的已有记录(PSM、协议 ID、服务名等匹配)
if (p_srec->psm == psm && p_srec->mx_proto_id == mx_proto_id &&
service_id == p_srec->service_id && p_name &&
/* 匹配发起方或接收方服务名 */
(!strncmp(p_name, (char*)p_srec->orig_service_name,
/* strlcpy replaces end char with termination char*/
BT_MAX_SERVICE_NAME_LEN - 1) ||
!strncmp(p_name, (char*)p_srec->term_service_name,
/* strlcpy replaces end char with termination char*/
BT_MAX_SERVICE_NAME_LEN - 1))) {
record_allocated = true;
break;
}
}
/* Mark the first available service record */
else if (!record_allocated) {
// 找到第一个未使用的记录,清零并标记
*p_srec = {};
record_allocated = true;
first_unused_record = index;
}
}
if (!record_allocated) {
log::warn("Out of Service Records ({})", BTM_SEC_MAX_SERVICE_RECORDS);
return (record_allocated); // 无可用记录时失败
}
/* Process the request if service record is valid */
/* If a duplicate service wasn't found, use the first available */
if (index >= BTM_SEC_MAX_SERVICE_RECORDS) {
index = first_unused_record;
p_srec = &sec_serv_rec[index];
}
p_srec->psm = psm;
p_srec->service_id = service_id;
p_srec->mx_proto_id = mx_proto_id;
// 2. 安全级别配置(发起方 vs 接收方)
if (is_originator) { // 场景一:连接发起方
// 存储发起方通道 ID 和服务名
p_srec->orig_mx_chan_id = mx_chan_id;
strlcpy((char*)p_srec->orig_service_name, p_name,
BT_MAX_SERVICE_NAME_LEN + 1);
// 清除接收方相关的旧标志(确保仅保留发起方策略)
/* clear out the old setting, just in case it exists */
{
p_srec->security_flags &=
~(BTM_SEC_OUT_ENCRYPT | BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_MITM);
}
// 安全模式适配:SP/SC 模式下,认证必须伴随 MITM 保护
/* Parameter validation. Originator should not set requirements for
* incoming connections */
sec_level &= ~(BTM_SEC_IN_ENCRYPT | BTM_SEC_IN_AUTHENTICATE |
BTM_SEC_IN_MITM | BTM_SEC_IN_MIN_16_DIGIT_PIN);
if (security_mode == BTM_SEC_MODE_SP || security_mode == BTM_SEC_MODE_SC) {
if (sec_level & BTM_SEC_OUT_AUTHENTICATE) sec_level |= BTM_SEC_OUT_MITM;
}
// 协议强制规则:加密必须开启认证
/* Make sure the authenticate bit is set, when encrypt bit is set */
if (sec_level & BTM_SEC_OUT_ENCRYPT) sec_level |= BTM_SEC_OUT_AUTHENTICATE;
/* outgoing connections usually set the security level right before
* the connection is initiated.
* set it to be the outgoing service */
p_out_serv = p_srec; // 更新全局发起方服务指针
} else { // 场景二:连接接收方
// 存储接收方通道 ID 和服务名
p_srec->term_mx_chan_id = mx_chan_id;
strlcpy((char*)p_srec->term_service_name, p_name,BT_MAX_SERVICE_NAME_LEN + 1);
// 清除发起方相关的旧标志(确保仅保留接收方策略)
/* clear out the old setting, just in case it exists */
{
p_srec->security_flags &=
~(BTM_SEC_IN_ENCRYPT | BTM_SEC_IN_AUTHENTICATE | BTM_SEC_IN_MITM |
BTM_SEC_IN_MIN_16_DIGIT_PIN);
}
// 过滤发起方参数(接收方不应设置发起方规则)
/* Parameter validation. Acceptor should not set requirements for outgoing
* connections */
sec_level &=
~(BTM_SEC_OUT_ENCRYPT | BTM_SEC_OUT_AUTHENTICATE | BTM_SEC_OUT_MITM);
// 安全模式适配:逻辑与发起方对称
if (security_mode == BTM_SEC_MODE_SP || security_mode == BTM_SEC_MODE_SC) {
if (sec_level & BTM_SEC_IN_AUTHENTICATE) sec_level |= BTM_SEC_IN_MITM;
}
// 协议强制规则:逻辑与发起方对称
/* Make sure the authenticate bit is set, when encrypt bit is set */
if (sec_level & BTM_SEC_IN_ENCRYPT) sec_level |= BTM_SEC_IN_AUTHENTICATE;
}
// 3. 最终标志设置与
p_srec->security_flags |= (uint16_t)(sec_level | BTM_SEC_IN_USE);
log::debug(
"[{}]: id:{}, is_orig:{} psm:0x{:04x} proto_id:{} chan_id:{} : "
"sec:0x{:x} service_name:[{}] (up to {} chars saved)",
index, service_id, logbool(is_originator).c_str(), psm, mx_proto_id,
mx_chan_id, p_srec->security_flags, p_name, BT_MAX_SERVICE_NAME_LEN);
return (record_allocated);
}
AddService
函数是蓝牙安全管理模块(BTM)的核心方法,用于注册服务的安全策略。其主要职责包括:
-
管理服务安全记录:维护一个固定大小的安全服务记录数组(
sec_serv_rec
),支持复用或创建新记录。 -
设置安全级别:根据是否为连接发起方(
is_originator
),配置不同的安全标志(加密、认证、MITM 保护等)。 -
参数校验与策略适配:确保安全级别参数符合蓝牙协议规范(如加密必须伴随认证),并根据安全模式(SP/SC)调整策略。
L2CA_ConnectReq
packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc
/*******************************************************************************
*
* Function L2CA_ConnectReq
*
* Description Higher layers call this function to create an L2CAP
* connection.
* Note that the connection is not established at this time,
* but connection establishment gets started. The callback
* will be invoked when connection establishes or fails.
*
* Returns the CID of the connection, or 0 if it failed to start
*
******************************************************************************/
uint16_t L2CA_ConnectReq(uint16_t psm, const RawAddress& p_bd_addr) {
log::verbose("BDA {} PSM: 0x{:04x}", ADDRESS_TO_LOGGABLE_STR(p_bd_addr), psm);
// 1. 初始化与参数校验
/* Fail if we have not established communications with the controller */
if (!BTM_IsDeviceUp()) {
log::warn("BTU not ready");
return 0; // 蓝牙未启动,连接失败
}
/* Fail if the PSM is not registered */
tL2C_RCB* p_rcb = l2cu_find_rcb_by_psm(psm);
if (p_rcb == nullptr) {
log::warn("no RCB, PSM={}", loghex(psm));
return 0; // 目标 PSM 未注册,服务不可用
}
// 2. 链路控制块(LCB)管理
/* First, see if we already have a link to the remote */
/* assume all ERTM l2cap connection is going over BR/EDR for now */
tL2C_LCB* p_lcb = l2cu_find_lcb_by_bd_addr(p_bd_addr, BT_TRANSPORT_BR_EDR);
if (p_lcb == nullptr) {
// 无链路连接,创建新的 LCB 并启动链路建立流程
/* No link. Get an LCB and start link establishment */
p_lcb = l2cu_allocate_lcb(p_bd_addr, false, BT_TRANSPORT_BR_EDR);
/* currently use BR/EDR for ERTM mode l2cap connection */
if (p_lcb == nullptr) {
log::warn("connection not started for PSM={}, p_lcb={}", loghex(psm),
fmt::ptr(p_lcb));
return 0; // 分配 LCB 失败
}
l2cu_create_conn_br_edr(p_lcb); // 发起 BR/EDR 链路连接
}
// 3. 通道控制块(CCB)分配与配置
/* Allocate a channel control block */
tL2C_CCB* p_ccb = l2cu_allocate_ccb(p_lcb, 0);
if (p_ccb == nullptr) {
log::warn("no CCB, PSM={}", loghex(psm));
return 0;
}
/* Save registration info */
p_ccb->p_rcb = p_rcb; // 关联目标服务的 RCB
p_ccb->connection_initiator = L2CAP_INITIATOR_LOCAL; // 标记为本地发起连接
// 4. 根据链路状态触发连接流程
/* If link is up, start the L2CAP connection */
if (p_lcb->link_state == LST_CONNECTED) {
// 链路已连接,直接触发 L2CAP 连接请求
l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_REQ, nullptr);
} else if (p_lcb->link_state == LST_DISCONNECTING) {
/* If link is disconnecting, save link info to retry after disconnect
* Possible Race condition when a reconnect occurs
* on the channel during a disconnect of link. This
* ccb will be automatically retried after link disconnect
* arrives
*/
log::verbose("L2CAP API - link disconnecting: RETRY LATER");
// 链路正在断开,缓存 CCB 以便断开后重试
/* Save ccb so it can be started after disconnect is finished */
p_lcb->p_pending_ccb = p_ccb;
}
log::verbose("L2CAP - L2CA_conn_req(psm: 0x{:04x}) returned CID: 0x{:04x}",
psm, p_ccb->local_cid);
/* Return the local CID as our handle */
//无论链路是否立即建立,只要请求成功发起,即返回分配的本地 CID(非零值表示成功,0 表示失败)。后续通过 CID 管理通道状态,连接结果通过回调通知上层
return p_ccb->local_cid;
}
发起 L2CAP 连接请求。其主要流程包括:
-
校验蓝牙控制器状态:确保蓝牙已启动且可用。
-
查找协议服务复用器(PSM)对应的注册控制块(RCB):验证目标服务是否已注册。
-
管理链路控制块(LCB):处理与目标设备的链路连接(若未建立,则创建并启动链路建立流程)。
-
分配通道控制块(CCB):管理 L2CAP 通道的生命周期。
-
触发连接状态机:根据链路状态(已连接 / 断开中)执行相应操作,或缓存待处理的连接请求。
-
链路已连接:若链路状态为
LST_CONNECTED
,调用l2c_csm_execute
触发状态机处理L2CEVT_L2CA_CONNECT_REQ
事件,发送 L2CAP Connect Request PDU。
结果回调:bta_sdp_search_cback(BTA_SDP_SEARCH_COMP_EVT
)
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_act.cc
/** Callback from btm after search is completed */
static void bta_sdp_search_cback(UNUSED_ATTR const RawAddress& bd_addr,
tSDP_RESULT result, const void* user_data) {
// 1. 局部变量初始化
tBTA_SDP_STATUS status = BTA_SDP_FAILURE;
int count = 0;
log::verbose("res: 0x{:x}", result);
bta_sdp_cb.sdp_active = false;
// 2. 检查回调函数指针
if (bta_sdp_cb.p_dm_cback == NULL) return;
// 3. 提取 UUID
Uuid& uuid = *(reinterpret_cast<Uuid*>(const_cast<void*>(user_data)));
// 4. 初始化事件数据结构
tBTA_SDP_SEARCH_COMP evt_data;
memset(&evt_data, 0, sizeof(evt_data));
evt_data.remote_addr = bta_sdp_cb.remote_addr;
evt_data.uuid = uuid;
// 5. 处理搜索成功或数据库满的情况
if (result == SDP_SUCCESS || result == SDP_DB_FULL) {
tSDP_DISC_REC* p_rec = NULL;
do {
p_rec = get_legacy_stack_sdp_api()->db.SDP_FindServiceUUIDInDb(
p_bta_sdp_cfg->p_sdp_db, uuid, p_rec);
/* generate the matching record data pointer */
if (!p_rec) {
log::verbose("UUID not found");
continue;
}
// 根据不同的 UUID 生成相应的 SDP 记录
status = BTA_SDP_SUCCESS;
if (uuid == UUID_MAP_MAS) {
log::verbose("found MAP (MAS) uuid");
bta_create_mas_sdp_record(&evt_data.records[count], p_rec);
} else if (uuid == UUID_MAP_MNS) {
log::verbose("found MAP (MNS) uuid");
bta_create_mns_sdp_record(&evt_data.records[count], p_rec);
} else if (uuid == UUID_PBAP_PSE) {
log::verbose("found PBAP (PSE) uuid");
bta_create_pse_sdp_record(&evt_data.records[count], p_rec);
} else if (uuid == UUID_OBEX_OBJECT_PUSH) {
log::verbose("found Object Push Server (OPS) uuid");
bta_create_ops_sdp_record(&evt_data.records[count], p_rec);
} else if (uuid == UUID_SAP) {
log::verbose("found SAP uuid");
bta_create_sap_sdp_record(&evt_data.records[count], p_rec);
} else if (uuid == UUID_PBAP_PCE) { // 处理 PBAP PCE UUID 特殊情况
log::verbose("found PBAP (PCE) uuid");
if (p_rec != NULL) {
uint16_t peer_pce_version = 0;
get_legacy_stack_sdp_api()->record.SDP_FindProfileVersionInRec(
p_rec, UUID_SERVCLASS_PHONE_ACCESS, &peer_pce_version);
if (peer_pce_version != 0) {
btif_storage_set_pce_profile_version(p_rec->remote_bd_addr,
peer_pce_version);
}
} else {
log::verbose("PCE Record is null");
}
} else if (uuid == UUID_DIP) {
log::verbose("found DIP uuid");
bta_create_dip_sdp_record(&evt_data.records[count], p_rec);
} else {
/* we do not have specific structure for this */
log::verbose("profile not identified. using raw data");
bta_create_raw_sdp_record(&evt_data.records[count], p_rec);
p_rec = NULL; // Terminate loop
/* For raw, we only extract the first entry, and then return the
entire raw data chunk.
TODO: Find a way to split the raw data into record chunks, and
iterate to extract generic data for each chunk - e.g. rfcomm
channel and service name. */
}
count++;
} while (p_rec != NULL && count < BTA_SDP_MAX_RECORDS);
evt_data.record_count = count;
}
// 6. 完成事件数据结构设置
evt_data.status = status;
tBTA_SDP bta_sdp;
bta_sdp.sdp_search_comp = evt_data;
// 调用回调函数 bta_sdp_cb.p_dm_cback 通知搜索完成
bta_sdp_cb.p_dm_cback(BTA_SDP_SEARCH_COMP_EVT, &bta_sdp, (void*)&uuid);
bluetooth::shim::CountCounterMetrics(
android::bluetooth::CodePathCounterKeyEnum::SDP_SUCCESS, 1);
osi_free(const_cast<void*>(
user_data)); // We no longer need the user data to track the search
}
bta_sdp_search_cback
是一个回调函数,在 SDP搜索完成后由 btm
调用。主要任务是处理搜索结果,根据搜索结果生成相应的 SDP 记录,并将处理后的结果通过回调函数反馈给调用者,同时记录搜索成功的指标数据。
sdp_dm_cback(BTA_SDP_SEARCH_COMP_EVT)
packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
static void sdp_dm_cback(tBTA_SDP_EVT event, tBTA_SDP* p_data,
void* user_data) {
switch (event) {
case BTA_SDP_SEARCH_COMP_EVT: {
int size = sizeof(tBTA_SDP);
// 1. 计算所需内存大小
size += get_sdp_records_size(p_data->sdp_search_comp.records,
p_data->sdp_search_comp.record_count);
// 2. 深度复制记录内容
/* need to deep copy the record content */
btif_transfer_context(btif_sdp_search_comp_evt, event, (char*)p_data,
size, sdp_search_comp_copy_cb);
break;
}
case BTA_SDP_CREATE_RECORD_USER_EVT: {
on_create_record_event(PTR_TO_INT(user_data));
break;
}
case BTA_SDP_REMOVE_RECORD_USER_EVT: {
on_remove_record_event(PTR_TO_INT(user_data));
break;
}
default:
break;
}
}
sdp_dm_cback
是一个回调函数,用于处理蓝牙 SDP相关的事件。它根据不同的事件类型执行不同的操作,这里主要分析 BTA_SDP_SEARCH_COMP_EVT
事件。当接收到 BTA_SDP_SEARCH_COMP_EVT
事件时,首先计算存储 SDP 搜索结果所需的总内存大小,然后调用 btif_transfer_context
函数进行上下文转移和数据的深度复制。目的是为了在不同的线程或者上下文环境中安全地处理 SDP 搜索结果,避免数据在传递过程中被意外修改或者丢失。
btif_sdp_search_comp_evt
/packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
static void btif_sdp_search_comp_evt(uint16_t event, char* p_param) {
tBTA_SDP_SEARCH_COMP* evt_data = (tBTA_SDP_SEARCH_COMP*)p_param; // 提取事件数据
log::verbose("event = {}", event);
if (event != BTA_SDP_SEARCH_COMP_EVT) return;
HAL_CBACK(bt_sdp_callbacks, sdp_search_cb, (bt_status_t)evt_data->status,
evt_data->remote_addr, evt_data->uuid, evt_data->record_count,
evt_data->records);
}
处理蓝牙 SDP搜索完成事件。当接收到 SDP 搜索完成的通知时,该函数会对事件进行检查,并将搜索结果通过回调函数传递给上层模块。以便上层模块根据搜索结果进行相应的处理,如显示搜索到的服务信息、连接到特定的服务等。
2.3 SDP 记录创建流程
①上层接口调用:create_sdp_record
触发以下步骤:
-
调用
alloc_sdp_slot
分配空闲槽位,计算记录内存需求(含动态数据)并深拷贝数据。 -
通过
BTA_SdpCreateRecordByUser
将创建请求派发到主线程,触发bta_sdp_create_record
回调。
②协议栈处理
-
主线程通过
sdp_dm_cback
分发BTA_SDP_CREATE_RECORD_USER_EVT
事件,调用on_create_record_event
。 -
根据记录类型(如
SDP_TYPE_PBAP_PSE
)调用具体创建函数(如add_pbaps_sdp
),最终通过SDP_CreateRecord
在底层数据库创建记录,并调用SDP_AddAttribute
添加属性。
③内存管理
-
copy_sdp_records
实现动态数据深拷贝,确保结构体指针指向独立内存。 -
SDP_AddAttributeToRecord
管理属性存储,按 ID 排序并校验内存空间。
create_sdp_record
bt_status_t create_sdp_record(bluetooth_sdp_record* record,
int* record_handle) {
int handle;
// 1. 分配 SDP 槽位
handle = alloc_sdp_slot(record);
log::verbose("handle = 0x{:08x}", handle);
if (handle < 0) return BT_STATUS_FAIL;
// 2. 通知协议栈创建记录
BTA_SdpCreateRecordByUser(INT_TO_PTR(handle));
*record_handle = handle;
return BT_STATUS_SUCCESS;
}
在蓝牙 SDP服务器中创建一个新的服务记录。通过以下步骤完成记录创建:
-
分配 SDP 槽位:从预定义的槽位数组中获取一个空闲槽位,用于存储服务记录数据。
-
通知底层协议栈:通过
BTA_SdpCreateRecordByUser
函数告知蓝牙协议栈创建服务记录,并关联槽位句柄。 -
返回句柄:将新创建记录的句柄返回给调用者,以便后续操作(如更新、删除记录)。
alloc_sdp_slot
/packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
/* Reserve a slot in sdp_slots, copy data and set a reference to the copy.
* The record_data will contain both the record and any data pointed to by
* the record.
* Currently this covers:
* service_name string,
* user1_ptr and
* user2_ptr. */
static int alloc_sdp_slot(bluetooth_sdp_record* in_record) {
// 1. 计算记录内存大小
int record_size = get_sdp_records_size(in_record, 1);
// 2. 预分配内存并复制数据
/* We are optimists here, and preallocate the record.
* This is to reduce the time we hold the sdp_lock. */
bluetooth_sdp_record* record = (bluetooth_sdp_record*)osi_malloc(record_size);
copy_sdp_records(in_record, record, 1);
// 3. 加锁分配槽位
{
std::unique_lock<std::recursive_mutex> lock(sdp_lock); // 自动加锁(作用域内有效)
for (int i = 0; i < MAX_SDP_SLOTS; i++) {
if (sdp_slots[i].state == SDP_RECORD_FREE) {
sdp_slots[i].state = SDP_RECORD_ALLOCED;
sdp_slots[i].record_data = record; // 绑定记录数据到槽位
return i; // 返回槽位索引作为句柄
}
}
} // 作用域结束,锁自动释放
// 4. 分配失败处理
log::error("failed - no more free slots!");
/* Rearly the optimist is too optimistic, and cleanup is needed...*/
osi_free(record);
return -1;
}
在 SDP 服务器的槽位数组(sdp_slots
)中分配一个空闲槽位,用于存储蓝牙服务记录。其主要流程包括:
-
计算记录内存需求:根据输入的服务记录数据,计算所需的内存大小(包括记录本身和指向的数据)。
-
预分配内存:提前分配内存并复制记录数据,减少锁的持有时间,提升并发性能。
-
槽位分配:通过加锁遍历槽位数组,找到空闲槽位并绑定记录数据。
-
错误处理:若分配失败,释放预分配的内存并返回错误。
get_sdp_records_size
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
int get_sdp_records_size(bluetooth_sdp_record* in_record, int count) {
bluetooth_sdp_record* record = in_record; // 指向 SDP 记录数组的指针,用于获取每条记录的动态数据长度信息
// 1. 初始化总大小
int records_size = 0;
// 2. 遍历每条记录计算内存
int i;
for (i = 0; i < count; i++) {
record = &in_record[i];
records_size += sizeof(bluetooth_sdp_record); // 结构体自身大小
// 3. 服务名称字符串内存
records_size += record->hdr.service_name_length; // 字符串内容长度
if (record->hdr.service_name_length > 0) {
records_size++; /* + '\0' termination of string */
}
// 4. 用户自定义数据内存
records_size += record->hdr.user1_ptr_len; // user1_ptr 指向的数据长度
records_size += record->hdr.user2_ptr_len; // user2_ptr 指向的数据长度
}
return records_size;
}
计算指定数量的 SDP 记录及其关联动态数据的总内存需求,用于提前分配内存以完成深拷贝。具体计算内容包括:
-
结构体自身大小:每个
bluetooth_sdp_record
结构体的固定内存占用。 -
动态数据大小:结构体中动态分配的字符串(服务名称)和用户自定义数据(
user1_ptr
、user2_ptr
)的实际数据长度。 -
额外开销:服务名称字符串的终止符
\0
占用的额外字节。
copy_sdp_records
packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
/* Deep copy all content of in_records into out_records.
* out_records must point to a chunk of memory large enough to contain all
* the data. Use getSdpRecordsSize() to calculate the needed size. */
void copy_sdp_records(bluetooth_sdp_record* in_records,
bluetooth_sdp_record* out_records, int count) {
int i;
bluetooth_sdp_record* in_record;
bluetooth_sdp_record* out_record;
// 1. 内存布局初始化
// 从目标数组末尾开始向前分配空间,确保记录结构体和动态数据在内存中连续存储
char* free_ptr =(char*)(&out_records[count]); /* set pointer to after the last entry */
// 2. 逐记录复制
for (i = 0; i < count; i++) {
in_record = &in_records[i];
out_record = &out_records[i];
*out_record = *in_record; // 浅拷贝结构体成员
// 3. 服务名称深拷贝
if (in_record->hdr.service_name == NULL ||
in_record->hdr.service_name_length == 0) {
out_record->hdr.service_name = NULL;
out_record->hdr.service_name_length = 0;
} else {
// 修正目标指针指向新内存
out_record->hdr.service_name = free_ptr; // Update service_name pointer
// Copy string
memcpy(free_ptr, in_record->hdr.service_name,
in_record->hdr.service_name_length);
free_ptr += in_record->hdr.service_name_length; // 指针后移
*(free_ptr) = '\0'; // Set '\0' termination of string
free_ptr++; // 跳过终止符位置
}
// 4. 用户自定义数据深拷贝
// 处理 user1_ptr
if (in_record->hdr.user1_ptr != NULL) {
out_record->hdr.user1_ptr = (uint8_t*)free_ptr; // Update pointer // 修正目标指针
memcpy(free_ptr, in_record->hdr.user1_ptr,
in_record->hdr.user1_ptr_len); // Copy content
free_ptr += in_record->hdr.user1_ptr_len;
}
// 处理 user2_ptr(逻辑与 user1_ptr 一致)
if (in_record->hdr.user2_ptr != NULL) {
out_record->hdr.user2_ptr = (uint8_t*)free_ptr; // Update pointer
memcpy(free_ptr, in_record->hdr.user2_ptr,
in_record->hdr.user2_ptr_len); // Copy content
free_ptr += in_record->hdr.user2_ptr_len;
}
}
return;
}
将源 SDP 记录数据深拷贝到目标内存区域,确保目标数据独立于源数据,避免指针引用导致的内存问题。具体功能包括:
-
结构体成员浅拷贝:复制
bluetooth_sdp_record
结构体的基本成员(如服务 UUID、属性列表指针等)。 -
动态数据深拷贝:对结构体中指向动态分配内存的指针(如服务名称字符串、
user1_ptr
、user2_ptr
)进行内存复制,创建独立副本。 -
连续内存管理:将所有复制后的数据存储在目标内存的连续区域,通过指针偏移计算确保内存布局正确。
BTA_SdpCreateRecordByUser
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_api.cc
/*******************************************************************************
*
* Function BTA_SdpCreateRecordByUser
*
* Description This function is used to request a callback to create a SDP
* record. The registered callback will be called with event
* BTA_SDP_CREATE_RECORD_USER_EVT.
*
* Returns BTA_SDP_SUCCESS, if the request is being processed.
* BTA_SDP_FAILURE, otherwise.
*
******************************************************************************/
tBTA_SDP_STATUS BTA_SdpCreateRecordByUser(void* user_data) {
do_in_main_thread(FROM_HERE,
base::BindOnce(bta_sdp_create_record, user_data));
return BTA_SDP_SUCCESS;
}
请求创建一个 SDP(服务发现协议)记录。通过向主线程发送一个任务,让主线程调用 bta_sdp_create_record
函数来完成 SDP 记录的创建操作。该函数会触发之前注册的回调函数,回调事件为 BTA_SDP_CREATE_RECORD_USER_EVT
。
bta_sdp_record
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_act.cc
/*******************************************************************************
*
* Function bta_sdp_record
*
* Description Discovers all sdp records for an uuid on remote device
*
* Returns void
*
******************************************************************************/
void bta_sdp_create_record(void* user_data) {
if (bta_sdp_cb.p_dm_cback)
bta_sdp_cb.p_dm_cback(BTA_SDP_CREATE_RECORD_USER_EVT, NULL, user_data);
}
蓝牙协议栈中 SDP模块的核心回调函数,用于触发用户自定义的 SDP 记录创建流程。主要作用是将创建记录的请求传递给上层模块(如设备管理模块),通过预先注册的回调接口完成记录的实际创建或配置。
sdp_dm_cback(BTA_SDP_CREATE_RECORD_USER_EVT)
/packages/modules/Bluetooth/system/btif/src/btif_sdp.cc
static void sdp_dm_cback(tBTA_SDP_EVT event, tBTA_SDP* p_data,
void* user_data) {
switch (event) {
case BTA_SDP_SEARCH_COMP_EVT: {
int size = sizeof(tBTA_SDP);
size += get_sdp_records_size(p_data->sdp_search_comp.records,
p_data->sdp_search_comp.record_count);
/* need to deep copy the record content */
btif_transfer_context(btif_sdp_search_comp_evt, event, (char*)p_data,
size, sdp_search_comp_copy_cb);
break;
}
case BTA_SDP_CREATE_RECORD_USER_EVT: {
on_create_record_event(PTR_TO_INT(user_data)); // 转换句柄并触发上层回调
break;
}
case BTA_SDP_REMOVE_RECORD_USER_EVT: {
on_remove_record_event(PTR_TO_INT(user_data));
break;
}
default:
break;
}
}
sdp_dm_cback
是蓝牙 SDP 模块与设备管理模块(DM)之间的回调函数,用于处理 SDP 相关事件。通过匹配不同的事件类型(如服务搜索完成、创建记录请求等),调用相应的处理函数,实现跨模块的事件驱动逻辑。其核心职责包括:
-
事件分发:根据事件类型(
tBTA_SDP_EVT
)执行不同的处理分支。 -
数据处理:对服务搜索完成事件(
BTA_SDP_SEARCH_COMP_EVT
)进行内存深拷贝,确保数据跨模块安全传递。 -
用户回调触发:将创建 / 删除记录的事件传递给上层业务逻辑(如
on_create_record_event
)。
on_create_record_event
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
/******************************************************************************
* CALLBACK FUNCTIONS
* Called in BTA context to create/remove SDP records.
******************************************************************************/
typedef enum {
SDP_TYPE_RAW, // Used to carry raw SDP search data for unknown UUIDs
SDP_TYPE_MAP_MAS, // Message Access Profile - Server
SDP_TYPE_MAP_MNS, // Message Access Profile - Client (Notification Server)
SDP_TYPE_PBAP_PSE, // Phone Book Profile - Server
SDP_TYPE_PBAP_PCE, // Phone Book Profile - Client
SDP_TYPE_OPP_SERVER, // Object Push Profile
SDP_TYPE_SAP_SERVER, // SIM Access Profile
SDP_TYPE_DIP, // Device Identification Profile
SDP_TYPE_MPS // Multi-Profile Specification
} bluetooth_sdp_types;
void on_create_record_event(int id) {
/*
* 1) Fetch the record pointer, and change its state?
* 2) switch on the type to create the correct record
* 3) Update state on completion
* 4) What to do at fail?
* */
// 1. 函数参数和初始化
log::verbose("Sdp Server");
const sdp_slot_t* sdp_slot = start_create_sdp(id); // 获取指定 ID 对应的 SDP 槽位信息
tBTA_SERVICE_ID service_id = -1;
bluetooth_sdp_record* record; // 用于指向 SDP 记录数据
// 2. 检查记录是否有效
/* In the case we are shutting down, sdp_slot is NULL */
if (sdp_slot != nullptr && (record = sdp_slot->record_data) != nullptr) {
int handle = -1;
// 3. 根据记录类型创建 SDP 记录
switch (record->hdr.type) {
case SDP_TYPE_MAP_MAS:
handle = add_maps_sdp(&record->mas);
service_id = BTA_MAP_SERVICE_ID;
break;
case SDP_TYPE_MAP_MNS:
handle = add_mapc_sdp(&record->mns);
service_id = BTA_MN_SERVICE_ID;
break;
case SDP_TYPE_PBAP_PSE:
handle = add_pbaps_sdp(&record->pse);
service_id = BTA_PBAP_SERVICE_ID;
break;
case SDP_TYPE_OPP_SERVER:
handle = add_opps_sdp(&record->ops);
break;
case SDP_TYPE_SAP_SERVER:
handle = add_saps_sdp(&record->sap);
break;
case SDP_TYPE_PBAP_PCE:
handle = add_pbapc_sdp(&record->pce);
service_id = BTA_PCE_SERVICE_ID;
break;
case SDP_TYPE_MPS:
handle = add_mps_sdp(&record->mps);
break;
case SDP_TYPE_RAW:
if (record->hdr.rfcomm_channel_number > 0) {
handle = add_rfc_sdp_rec(record->hdr.service_name, record->hdr.uuid,
record->hdr.rfcomm_channel_number);
}
break;
default:
log::verbose("Record type {} is not supported", record->hdr.type);
break;
}
// 4. 处理记录创建成功的情况
if (handle != -1) {
set_sdp_handle(id, handle); // 将创建成功的记录句柄与记录 ID 关联起来
if (service_id > 0) {
/**
* {@link btif_enable_service} calls {@link btif_dm_enable_service}, which calls {@link
* btif_in_execute_service_request}.
* - {@link btif_enable_service} btif_enable_servicesets the mask {@link btif_enabled_services}.
* - {@link btif_dm_enable_service} invokes the java callback to return uuids based
* on the enabled services mask.
* - {@link btif_in_execute_service_request} gates the java callback in {@link
* btif_dm_enable_service}.
*/
btif_enable_service(service_id); // 启用该服务
}
}
}
}
on_create_record_event
函数是一个回调函数,在 BTA(蓝牙应用层)上下文环境中被调用,用于创建 SDP(服务发现协议)记录。它根据传入的记录 ID,获取对应的 SDP 记录信息,依据记录的类型调用不同的函数来创建具体的 SDP 记录,并在创建成功后更新相关状态和启用对应的服务。
这里以SDP_TYPE_RAW记录类型为例进行分析。SDP_TYPE_RAW用于携带未知通用唯一识别码(UUID)的原始 SDP 搜索数据。在蓝牙服务发现过程中,可能会遇到一些未识别的 UUID,此时可以使用
SDP_TYPE_RAW
类型来存储和处理这些原始数据,以便后续进一步分析或调试。
add_rfc_sdp_rec
/packages/modules/Bluetooth/system/btif/src/btif_sock_sdp.cc
// Adds an SDP record to the SDP database using the given |name|, |uuid|, and
// |channel|. Note that if the |uuid| is empty, the |uuid| will be set based
// upon the |channel| passed in.
int add_rfc_sdp_rec(const char* name, Uuid uuid, const int channel) {
if (uuid.IsEmpty()) {
switch (channel) {
case RESERVED_SCN_PBS: // PBAP Reserved port
uuid = UUID_PBAP_PSE;
break;
case RESERVED_SCN_OPS:
uuid = UUID_OBEX_OBJECT_PUSH;
break;
default:
uuid = UUID_SPP;
break;
}
}
return add_rfc_sdp_by_uuid(name, uuid, channel);
}
向 SDP数据库中添加一条 SDP 记录。接收服务名称(name
)、服务的 UUID(uuid
)以及 RFCOMM 通道号(channel
)作为参数。若传入的 UUID 为空,根据通道号为其设置合适的 UUID,最后调用 add_rfc_sdp_by_uuid
函数完成 SDP 记录的添加操作。
add_rfc_sdp_by_uuid
packages/modules/Bluetooth/system/btif/src/btif_sock_sdp.cc
// Adds an RFCOMM SDP record for a service with the given |name|, |uuid|, and
// |channel|. This function attempts to identify the type of the service based
// upon its |uuid|, and will override the |channel| with a reserved channel
// number if the |uuid| matches one of the preregistered bluez SDP records.
static int add_rfc_sdp_by_uuid(const char* name, const Uuid& uuid,
const int channel) {
log::verbose("uuid: {}, service_name: {}, channel: {}", uuid.ToString(), name,
channel);
/*
* Bluetooth Socket API relies on having preregistered bluez sdp records for
* HSAG, HFAG, OPP & PBAP that are mapped to rc chan 10, 11,12 & 19. Today
* HSAG and HFAG is routed to BRCM AG and are not using BT socket API so for
* now we will need to support OPP and PBAP to enable 3rd party developer apps
* running on BRCM Android.
*
* To do this we will check the UUID for the requested service and mimic the
* SDP records of bluez upon reception. See functions add_opush() and
* add_pbap() in sdptool.c for actual records.
*/
// 1. 确定最终通道号
int final_channel = get_reserved_rfc_channel(uuid);
if (final_channel == -1) { // 表示没有为该 uuid找到保留的通道号
final_channel = channel;
}
// 2. 根据uuid调用不同的添加记录函数
int handle = 0;
if (uuid == UUID_OBEX_OBJECT_PUSH) {
handle = add_ops_sdp(name, final_channel);
} else if (uuid == UUID_PBAP_PSE) {
// PBAP Server is always channel 19
handle = add_pbap_sdp(name, final_channel);
} else if (uuid == UUID_SPP) {
handle = add_spp_sdp(name, final_channel);
} else if (uuid == UUID_MAP_MAS) {
// Record created by new SDP create record interface
handle = 0xff;
} else {
handle = add_sdp_by_uuid(name, uuid, final_channel);
}
return handle;
}
为具有指定名称(name
)、通用唯一识别码(uuid
)和通道号(channel
)的服务添加一个 RFCOMM(Radio Frequency Communication,射频通信)的 SDP记录。根据 uuid
识别服务类型,若 uuid
匹配预注册的 BlueZ SDP 记录,会用保留的通道号覆盖传入的 channel
,最后根据不同的 uuid
调用相应的函数来添加 SDP 记录,并返回记录的句柄。
这里主要分析调用 add_sdp_by_uuid
函数添加通用的 SDP 记录的情况。
add_sdp_by_uuid
packages/modules/Bluetooth/system/btif/src/btif_sock_sdp.cc
// Registers a service with the given |name|, |uuid|, and |channel| in the SDP
// database as a generic L2CAP RFCOMM protocol, storing its |uuid| as a service
// class sequence.
static int add_sdp_by_uuid(const char* name, const Uuid& uuid,
const uint16_t channel) {
log::verbose("uuid: {}, scn: {}, service_name: {}", uuid.ToString(), channel,
name);
// 1. 记录创建
uint32_t handle = get_legacy_stack_sdp_api()->handle.SDP_CreateRecord();
if (handle == 0) {
log::error("failed to create sdp record, scn: {}, service_name: {}",
channel, name);
return 0;
}
// 2. 数据准备
// Convert the |uuid| into a big-endian representation and add it as a
// sequence.
uint8_t type = UUID_DESC_TYPE;
uint8_t type_len = UUID_MAX_LENGTH;
uint8_t type_buf[48];
// Store the address of type buf in a pointer on the stack, so we can pass
// a double pointer to SDP_AddSequence
uint8_t* type_buf_ptr = type_buf;
uint8_t* tmp = type_buf;
// 3. 创建基本 SDP 记录
// Create the base SDP record.
const char* stage = "create_base_record";
if (!create_base_record(handle, name, channel, false /* with_obex */))
goto error;
// Do the conversion to big-endian -- tmp is only used to iterate through the
// UUID array in the macro and serves no other purpose as the conversion
// macros are not hygenic.
// 4. UUID 转换为大端格式
{ ARRAY_TO_BE_STREAM(tmp, uuid.To128BitBE().data(), UUID_MAX_LENGTH); }
// 5. 添加服务类序列
stage = "service_class_sequence";
if (!get_legacy_stack_sdp_api()->handle.SDP_AddSequence(
handle, (uint16_t)ATTR_ID_SERVICE_CLASS_ID_LIST, 1, &type, &type_len,
&type_buf_ptr))
goto error;
// 6. 输出成功日志并写入 EIR
log::verbose(
"service registered successfully, service_name: {}, handle: 0x{:08x}",
name, handle);
{
// Write the custom 128-bit UUID to EIR
tBTA_CUSTOM_UUID curr = {uuid, handle};
bta_sys_add_cust_uuid(curr); // 将自定义的 128 位 UUID 写入 EIR
}
return handle;
error:
get_legacy_stack_sdp_api()->handle.SDP_DeleteRecord(handle);
log::error("failed to register service stage: {}, service_name: {}", stage,
name);
return 0;
}
在 SDP数据库中注册一个服务。该服务使用通用的 L2CAP RFCOMM 协议,会将传入的 uuid
作为服务类序列存储。会创建 SDP 记录,添加基本信息和服务类序列,并在成功时将自定义的 128 位 UUID 写入 EIR(Extended Inquiry Response),最后返回服务记录的句柄;若过程中出现错误,则删除已创建的记录并返回 0。
SDP_CreateRecord
/*******************************************************************************
*
* Function SDP_CreateRecord
*
* Description This function is called to create a record in the database.
* This would be through the SDP database maintenance API. The
* record is created empty, teh application should then call
* "add_attribute" to add the record's attributes.
*
* Returns Record handle if OK, else 0.
*
******************************************************************************/
uint32_t SDP_CreateRecord(void) {
uint32_t handle;
uint8_t buf[4];
tSDP_DB* p_db = &sdp_cb.server_db;
// 1. 记录池可用性检查
/* First, check if there is a free record */
if (p_db->num_records < SDP_MAX_RECORDS) {
// 2. 记录结构体初始化
memset(&p_db->record[p_db->num_records], 0, sizeof(tSDP_RECORD));
// 3. 生成记录句柄
/* We will use a handle of the first unreserved handle plus last record
** number + 1 */
if (p_db->num_records)
handle = p_db->record[p_db->num_records - 1].record_handle + 1;
else
handle = 0x10000;
p_db->record[p_db->num_records].record_handle = handle;
p_db->num_records++;
log::verbose("SDP_CreateRecord ok, num_records:{}", p_db->num_records);
// 4. 自动添加句柄属性
/* Add the first attribute (the handle) automatically */
UINT32_TO_BE_FIELD(buf, handle);
SDP_AddAttribute(handle, ATTR_ID_SERVICE_RECORD_HDL, UINT_DESC_TYPE, 4,
buf);
return (p_db->record[p_db->num_records - 1].record_handle);
} else
log::error("SDP_CreateRecord fail, exceed maximum records:{}",
SDP_MAX_RECORDS);
return (0);
}
用于创建一个空的 SDP 记录。其主要流程包括:
-
记录池管理:检查是否有可用记录槽位(不超过
SDP_MAX_RECORDS
)。 -
句柄生成:为新记录分配唯一的句柄(Handle),基于前一条记录句柄递增或从初始值
0x10000
开始。 -
初始化记录:清空记录结构体,自动添加
ATTR_ID_SERVICE_RECORD_HDL
属性(存储句柄值)。 -
返回句柄:成功时返回新记录句柄,失败时返回
0
(如记录池已满)。
SDP_AddAttribute
packages/modules/Bluetooth/system/stack/sdp/sdp_db.cc
/*******************************************************************************
*
* Function SDP_AddAttribute
*
* Description This function is called to add an attribute to a record.
* This would be through the SDP database maintenance API.
* If the attribute already exists in the record, it is
* replaced with the new value.
*
* NOTE Attribute values must be passed as a Big Endian stream.
*
* Returns true if added OK, else false
*
******************************************************************************/
bool SDP_AddAttribute(uint32_t handle, uint16_t attr_id, uint8_t attr_type,
uint32_t attr_len, uint8_t* p_val) {
uint16_t zz;
tSDP_RECORD* p_rec = &sdp_cb.server_db.record[0]; // 指向 SDP 记录数组首元素
// sdp_cb.server_db 是全局的 SDP 服务器数据库,存储所有已创建的 SDP 记录
// 1. 空指针校验
if (p_val == nullptr) {
log::warn("Trying to add attribute with p_val == nullptr, skipped");
return (false);
}
// 2. 日志格式化输出
// TODO(305066880): invoke would_log when implemented to check
// if LOG_VERBOSE is displayed.
if (true) {
if ((attr_type == UINT_DESC_TYPE) ||
(attr_type == TWO_COMP_INT_DESC_TYPE) ||
(attr_type == UUID_DESC_TYPE) ||
(attr_type == DATA_ELE_SEQ_DESC_TYPE) ||
(attr_type == DATA_ELE_ALT_DESC_TYPE)) {
#define MAX_ARR_LEN 200
// one extra byte for storing terminating zero byte
// 将二进制数据转换为十六进制字符串(如 UUID 的大端字节流)
char num_array[2 * MAX_ARR_LEN + 1] = {0};
uint32_t len = (attr_len > MAX_ARR_LEN) ? MAX_ARR_LEN : attr_len;
#undef MAX_ARR_LEN
for (uint32_t i = 0; i < len; i++) {
snprintf(&num_array[i * 2], sizeof(num_array) - i * 2, "%02X",
(uint8_t)(p_val[i]));
}
log::verbose(
"SDP_AddAttribute: handle:{:X}, id:{:04X}, type:{}, len:{}, "
"p_val:{}, *p_val:{}",
handle, attr_id, attr_type, attr_len, fmt::ptr(p_val), num_array);
} else if (attr_type == BOOLEAN_DESC_TYPE) {
log::verbose(
"SDP_AddAttribute: handle:{:X}, id:{:04X}, type:{}, len:{}, "
"p_val:{}, *p_val:{}",
handle, attr_id, attr_type, attr_len, fmt::ptr(p_val), *p_val);
} else if ((attr_type == TEXT_STR_DESC_TYPE) ||
(attr_type == URL_DESC_TYPE)) {
if (p_val[attr_len - 1] == '\0') {
log::verbose(
"SDP_AddAttribute: handle:{:X}, id:{:04X}, type:{}, len:{}, "
"p_val:{}, *p_val:{}",
handle, attr_id, attr_type, attr_len, fmt::ptr(p_val),
(char*)p_val);
} else {
log::verbose(
"SDP_AddAttribute: handle:{:X}, id:{:04X}, type:{}, len:{}, "
"p_val:{}",
handle, attr_id, attr_type, attr_len, fmt::ptr(p_val));
}
} else {
log::verbose(
"SDP_AddAttribute: handle:{:X}, id:{:04X}, type:{}, len:{}, p_val:{}",
handle, attr_id, attr_type, attr_len, fmt::ptr(p_val));
}
}
// 3. 数据库查找目标记录
/* Find the record in the database */
for (zz = 0; zz < sdp_cb.server_db.num_records; zz++, p_rec++) {
if (p_rec->record_handle == handle) { // 匹配记录句柄
// 校验记录剩余空间
// error out early, no need to look up
if (p_rec->free_pad_ptr >= SDP_MAX_PAD_LEN) {
log::error(
"the free pad for SDP record with handle {} is full, skip adding "
"the attribute",
handle);
return (false);
}
// 调用底层函数添加属性
return SDP_AddAttributeToRecord(p_rec, attr_id, attr_type, attr_len,
p_val);
}
}
return (false);
}
向指定 SDP 记录中添加或更新属性。其核心职责包括:
-
属性唯一性管理:若属性已存在,覆盖原有值;若不存在,新增属性并按 ID 排序存储。
-
数据格式校验:确保属性值以大端格式(Big Endian)传递,符合 SDP 协议规范,并对不同类型属性(如 UUID、文本、布尔值等)进行差异化日志处理。
-
数据库遍历与操作:通过记录句柄查找目标记录,调用底层函数
SDP_AddAttributeToRecord
完成属性的实际存储。 -
内存安全控制:检查记录剩余空间(
free_pad_ptr
),避免缓冲区溢出,对文本类型支持截断处理。
SDP_AddAttributeToRecord
/home/yanlongli/code/android/packages/modules/Bluetooth/system/stack/sdp/sdp_db.cc
*******************************************************************************
*
* Function SDP_AddAttributeToRecord
*
* Description This function is called to add an attribute to a record.
* This would be through the SDP database maintenance API.
* If the attribute already exists in the record, it is
* replaced with the new value.
*
* NOTE Attribute values must be passed as a Big Endian stream.
*
* Returns true if added OK, else false
*
******************************************************************************/
bool SDP_AddAttributeToRecord(tSDP_RECORD* p_rec, uint16_t attr_id,
uint8_t attr_type, uint32_t attr_len,
uint8_t* p_val) {
uint16_t xx, yy;
tSDP_ATTRIBUTE* p_attr = &p_rec->attribute[0];
// 1. 查找属性是否存在
/* Found the record. Now, see if the attribute already exists */
for (xx = 0; xx < p_rec->num_attributes; xx++, p_attr++) {
/* The attribute exists. replace it */
if (p_attr->id == attr_id) {
SDP_DeleteAttributeFromRecord(p_rec, attr_id); // 先删除旧属性
break;
}
if (p_attr->id > attr_id) break; // 属性数组按 ID 升序排列,提前终止查找
}
// 2. 校验属性数量限制
if (p_rec->num_attributes >= SDP_MAX_REC_ATTR) return (false);
// 3. 确定属性插入位置
/* If not found, see if we can allocate a new entry */
if (xx == p_rec->num_attributes) // 未找到属性,追加到数组末尾
p_attr = &p_rec->attribute[p_rec->num_attributes];
else { // 插入到有序数组的指定位置,后续属性后移
/* Since the attributes are kept in sorted order, insert ours here */
for (yy = p_rec->num_attributes; yy > xx; yy--)
p_rec->attribute[yy] = p_rec->attribute[yy - 1];
}
p_attr->id = attr_id;
p_attr->type = attr_type;
p_attr->len = attr_len;
if (p_rec->free_pad_ptr + attr_len >= SDP_MAX_PAD_LEN) {
if (p_rec->free_pad_ptr >= SDP_MAX_PAD_LEN) {
log::error(
"SDP_AddAttributeToRecord failed: free pad {} equals or exceeds max "
"padding length {}",
p_rec->free_pad_ptr, SDP_MAX_PAD_LEN);
return (false);
}
// 4. 内存空间校验与分配
/* do truncate only for text string type descriptor */
if (attr_type == TEXT_STR_DESC_TYPE) {
log::warn(
"SDP_AddAttributeToRecord: attr_len:{} too long. truncate to ({})",
attr_len, SDP_MAX_PAD_LEN - p_rec->free_pad_ptr);
attr_len = SDP_MAX_PAD_LEN - p_rec->free_pad_ptr;
p_val[SDP_MAX_PAD_LEN - p_rec->free_pad_ptr - 1] = '\0';
} else
attr_len = 0;
}
if (attr_len > 0) {
p_attr->len = attr_len;
memcpy(&p_rec->attr_pad[p_rec->free_pad_ptr], p_val, (size_t)attr_len); // 写入属性值
p_attr->value_ptr = &p_rec->attr_pad[p_rec->free_pad_ptr]; // 记录值指针
p_rec->free_pad_ptr += attr_len; // 更新剩余空间指针
} else if (attr_len == 0 && p_attr->len != 0) {
/* if truncate to 0 length, simply don't add */
log::error(
"SDP_AddAttributeToRecord fail, length exceed maximum: ID {}: "
"attr_len:{}",
attr_id, attr_len);
p_attr->id = p_attr->type = p_attr->len = 0;
return (false);
}
// 5. 更新属性计数并返回
p_rec->num_attributes++;
return (true);
}
向指定 SDP 记录中添加或更新属性。其核心逻辑包括:
-
属性唯一性检查:若属性已存在,先删除旧属性再插入新值;若不存在,按属性 ID 排序插入合适位置。
-
内存分配与校验:确保属性值大小不超过记录的可用空间(
SDP_MAX_PAD_LEN
),对文本类型支持截断处理。 -
数据持久化:将属性值存储到记录的连续内存块(
attr_pad
)中,并更新属性指针和剩余空间。
SDP_AddSequence
packages/modules/Bluetooth/system/stack/sdp/sdp_db.cc
/*******************************************************************************
*
* Function SDP_AddSequence
*
* Description This function is called to add a sequence to a record.
* This would be through the SDP database maintenance API.
* If the sequence already exists in the record, it is replaced
* with the new sequence.
*
* NOTE Element values must be passed as a Big Endian stream.
*
* Returns true if added OK, else false
*
******************************************************************************/
bool SDP_AddSequence(uint32_t handle, uint16_t attr_id, uint16_t num_elem,
uint8_t type[], uint8_t len[], uint8_t* p_val[]) {
uint16_t xx;
uint8_t* p;
uint8_t* p_head;
bool result;
// 1. 内存分配与初始化
uint8_t* p_buff =
(uint8_t*)osi_malloc(sizeof(uint8_t) * SDP_MAX_ATTR_LEN * 2);
p = p_buff;
// 2. 构建序列
/* First, build the sequence */
for (xx = 0; xx < num_elem; xx++) {
p_head = p;
switch (len[xx]) {
case 1:
UINT8_TO_BE_STREAM(p, (type[xx] << 3) | SIZE_ONE_BYTE);
break;
case 2:
UINT8_TO_BE_STREAM(p, (type[xx] << 3) | SIZE_TWO_BYTES);
break;
case 4:
UINT8_TO_BE_STREAM(p, (type[xx] << 3) | SIZE_FOUR_BYTES);
break;
case 8:
UINT8_TO_BE_STREAM(p, (type[xx] << 3) | SIZE_EIGHT_BYTES);
break;
case 16:
UINT8_TO_BE_STREAM(p, (type[xx] << 3) | SIZE_SIXTEEN_BYTES);
break;
default:
UINT8_TO_BE_STREAM(p, (type[xx] << 3) | SIZE_IN_NEXT_BYTE);
UINT8_TO_BE_STREAM(p, len[xx]);
break;
}
ARRAY_TO_BE_STREAM(p, p_val[xx], len[xx]);
if (p - p_buff > SDP_MAX_ATTR_LEN) {
/* go back to before we add this element */
p = p_head;
if (p_head == p_buff) {
/* the first element exceed the max length */
log::error("SDP_AddSequence - too long(attribute is not added)!!");
osi_free(p_buff);
return false;
} else
log::error("SDP_AddSequence - too long, add {} elements of {}", xx,
num_elem);
break;
}
}
// 3. 添加序列到记录
result = SDP_AddAttribute(handle, attr_id, DATA_ELE_SEQ_DESC_TYPE,
(uint32_t)(p - p_buff), p_buff);
osi_free(p_buff);
return result;
}
向 SDP记录中添加一个序列。该函数属于 SDP 数据库维护 API 的一部分,若指定的序列已存在于记录中,会用新的序列替换它。函数要求元素值以大端字节序(Big Endian)的流形式传入,最终返回操作是否成功的布尔值。
SDP_AddAttribute前面已分析,不在赘述。
2.4 SDP 记录删除流程
①上层触发删除:remove_sdp_record
执行以下操作:
-
校验记录 ID 有效性,通过互斥锁获取记录类型,禁用关联的蓝牙服务(如
BTA_PBAP_SERVICE_ID
)。 -
调用
free_sdp_slot
释放槽位,通过BTA_SdpRemoveRecordByUser
触发主线程删除。
②协议栈删除逻辑
-
主线程通过
bta_sdp_remove_record
回调触发BTA_SDP_REMOVE_RECORD_USER_EVT
,on_remove_record_event
调用SDP_DeleteRecord
。 -
SDP_DeleteRecord
根据句柄(handle
)执行单记录或全量删除:-
单记录删除:遍历数组找到目标记录,前移后续记录以保持连续,更新主 DI 句柄(若必要)。
-
全量删除:直接重置记录数与主 DI 句柄。
-
③数据一致性:删除过程中调整属性指针(value_ptr
)以适应数组重组,但存在潜在逻辑错误(实际无需调整,因指针为记录内相对偏移)。
remove_sdp_record
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
bt_status_t remove_sdp_record(int record_id) {
int handle;
// 1. 参数有效性校验
if (record_id >= MAX_SDP_SLOTS) {
return BT_STATUS_PARM_INVALID;
}
bluetooth_sdp_record* record;
bluetooth_sdp_types sdp_type = SDP_TYPE_RAW;
// 2. 线程安全的记录获取
{
std::unique_lock<std::recursive_mutex> lock(sdp_lock);
record = sdp_slots[record_id].record_data;
if (record != NULL) {
sdp_type = record->hdr.type; // 获取记录类型
}
}
// 3. 服务类型匹配与服务禁用
tBTA_SERVICE_ID service_id = -1;
switch (sdp_type) {
case SDP_TYPE_MAP_MAS:
service_id = BTA_MAP_SERVICE_ID;
break;
case SDP_TYPE_MAP_MNS:
service_id = BTA_MN_SERVICE_ID;
break;
case SDP_TYPE_PBAP_PSE:
service_id = BTA_PBAP_SERVICE_ID;
break;
case SDP_TYPE_PBAP_PCE:
service_id = BTA_PCE_SERVICE_ID;
break;
default:
/* other enumeration values were not enabled in {@link on_create_record_event} */
break;
}
if (service_id > 0) {
// {@link btif_disable_service} sets the mask {@link btif_enabled_services}.
btif_disable_service(service_id); // 禁用对应的蓝牙服务
}
// 4. 释放 SDP 槽位
/* Get the Record handle, and free the slot */
handle = free_sdp_slot(record_id); // 释放槽位并获取记录句柄
log::verbose("Sdp Server id={} to handle=0x{:08x}", record_id, handle);
/* Pass the actual record handle */
if (handle > 0) {
BTA_SdpRemoveRecordByUser(INT_TO_PTR(handle)); // 通过 BTA 接口删除记录
return BT_STATUS_SUCCESS;
}
log::verbose("Sdp Server - record already removed - or never created");
return BT_STATUS_FAIL;
}
从蓝牙 SDP数据库中删除指定 ID 的服务记录。其核心流程包括:
-
参数校验:检查记录 ID 是否在有效范围内。
-
线程安全访问:通过互斥锁确保多线程环境下对 SDP 槽位的安全操作。
-
服务类型识别:根据记录类型获取对应的服务 ID,以便禁用相关服务。
-
资源释放:释放 SDP 槽位,并通过 BTA 接口触发记录删除。
-
状态同步:更新服务启用状态,确保协议栈与上层逻辑一致。
BTA_SdpRemoveRecordByUser
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_api.cc
/*******************************************************************************
*
* Function BTA_SdpRemoveRecordByUser
*
* Description This function is used to request a callback to remove a SDP
* record. The registered callback will be called with event
* BTA_SDP_REMOVE_RECORD_USER_EVT.
*
* Returns BTA_SDP_SUCCESS, if the request is being processed.
* BTA_SDP_FAILURE, otherwise.
*
******************************************************************************/
tBTA_SDP_STATUS BTA_SdpRemoveRecordByUser(void* user_data) {
do_in_main_thread(FROM_HERE,
base::BindOnce(bta_sdp_remove_record, user_data));
return BTA_SDP_SUCCESS;
}
蓝牙协议栈(BTA)中用于请求删除 SDP 记录的接口。其核心作用是将删除记录的请求提交到主线程处理,并通过回调机制通知上层逻辑操作结果。具体流程包括:
-
异步处理封装:通过
do_in_main_thread
将删除操作派发到主线程执行,避免在当前线程阻塞。 -
回调触发:当删除操作完成后,上层注册的回调函数(如
sdp_dm_cback
)会收到BTA_SDP_REMOVE_RECORD_USER_EVT
事件,从而更新状态或释放资源。
bta_sdp_remove_record
packages/modules/Bluetooth/system/bta/sdp/bta_sdp_act.cc
/*******************************************************************************
*
* Function bta_sdp_create_record
*
* Description Discovers all sdp records for an uuid on remote device
*
* Returns void
*
******************************************************************************/
void bta_sdp_remove_record(void* user_data) {
if (bta_sdp_cb.p_dm_cback)
bta_sdp_cb.p_dm_cback(BTA_SDP_REMOVE_RECORD_USER_EVT, NULL, user_data);
}
触发回调函数,以通知上层应用程序有一个 SDP(服务发现协议)记录删除事件发生。当 BTA_SdpRemoveRecordByUser
函数将删除 SDP 记录的请求派发到主线程后,主线程会调用此函数来处理该请求,最终通过回调机制通知上层应用。
on_remove_record_event
packages/modules/Bluetooth/system/btif/src/btif_sdp_server.cc
void on_remove_record_event(int handle) {
log::verbose("Sdp Server");
// User data carries the actual SDP handle, not the ID.
if (handle != -1 && handle != 0) {
bool result;
result = get_legacy_stack_sdp_api()->handle.SDP_DeleteRecord(handle);
if (!result) {
log::error("Unable to remove handle 0x{:08x}", handle);
}
}
}
SDP模块的回调处理函数,用于响应 SDP 记录删除事件。其核心职责是:
-
校验记录句柄的有效性。
-
调用底层 API 删除指定句柄的 SDP 记录。
-
记录操作结果日志,便于调试和故障排查。
SDP_DeleteRecord
/*******************************************************************************
*
* Function SDP_DeleteRecord
*
* Description This function is called to add a record (or all records)
* from the database. This would be through the SDP database
* maintenance API.
*
* If a record handle of 0 is passed, all records are deleted.
*
* Returns true if succeeded, else false
*
******************************************************************************/
bool SDP_DeleteRecord(uint32_t handle) {
uint16_t xx, yy, zz;
tSDP_RECORD* p_rec = &sdp_cb.server_db.record[0];
// 1. 全量删除逻辑
if (handle == 0 || sdp_cb.server_db.num_records == 0) {
/* Delete all records in the database */
sdp_cb.server_db.num_records = 0;
/* require new DI record to be created in SDP_SetLocalDiRecord */
sdp_cb.server_db.di_primary_handle = 0;
return (true);
}
// 2. 单记录删除逻辑
else {
/* Find the record in the database */
for (xx = 0; xx < sdp_cb.server_db.num_records; xx++, p_rec++) {
if (p_rec->record_handle == handle) { // 找到目标记录
// 重组记录数组:将后续记录前移
/* Found it. Shift everything up one */
for (yy = xx; yy < sdp_cb.server_db.num_records - 1; yy++, p_rec++) {
*p_rec = *(p_rec + 1); // 复制后续记录
/* Adjust the attribute value pointer for each attribute */
for (zz = 0; zz < p_rec->num_attributes; zz++)
p_rec->attribute[zz].value_ptr -= sizeof(tSDP_RECORD);
}
sdp_cb.server_db.num_records--;
log::verbose("SDP_DeleteRecord ok, num_records:{}",
sdp_cb.server_db.num_records);
/* if we're deleting the primary DI record, clear the */
/* value in the control block */
if (sdp_cb.server_db.di_primary_handle == handle) {// 更新主 DI 句柄(若删除的是主记录)
sdp_cb.server_db.di_primary_handle = 0;
}
return (true);
}
}
}
return (false);
}
删除单个或所有 SDP 记录。其核心逻辑包括:
-
全量删除处理:当传入句柄为
0
时,清空整个数据库。 -
单记录删除处理:根据句柄查找并删除指定记录,重组记录数组以保持连续性。
-
元数据更新:更新数据库记录数量、主设备信息(DI)句柄等状态。
三、时序图
3.1 SDP 服务初始化流程时序图
3.2 SDP 服务搜索流程时序图
3.3 创建SDP记录流程时序图
3.4 删除SDP记录流程时序图
四、总结
本文围绕蓝牙 SDP 服务记录的生命周期,详细解析了从初始化、创建、搜索到删除的完整流程。核心设计包括:
-
线程安全:通过主线程任务派发与互斥锁确保操作原子性。
-
解耦与抽象:通过接口结构体(
sdp_if
)和回调机制分离底层实现与上层逻辑。 -
协议兼容性:支持旧版协议栈接口(
get_legacy_stack_sdp_api
),并通过类型枚举(bluetooth_sdp_types
)适配多种服务类型。 -
内存管理:深拷贝与动态内存分配确保数据独立,避免悬垂指针。