【Bluedroid】藍牙 SDP(服務發現協議)模塊代碼解析與流程梳理

本文深入剖析Bluedroid藍牙協議棧中 SDP(服務發現協議)服務記錄的全生命周期管理流程,涵蓋初始化、記錄創建、服務搜索、記錄刪除等核心環節。通過解析代碼邏輯與數據結構,揭示各模塊間的協作機制,包括線程安全設計、回調機制及協議棧交互細節,為藍牙服務開發與調試提供系統性參考。

一、概述

藍牙SDP協議用于設備間服務發現,其實現分為四層:

  1. 接口層(btif):提供btif_sdp_get_interface接口,封裝initsearchcreate_sdp_record等操作。

  2. 業務邏輯層(BTA):通過BTA_SdpSearchBTA_SdpCreateRecordByUser派發任務到主線程。

  3. 協議棧層(Stack):實現SDP數據庫管理(SDP_CreateRecordSDP_AddAttribute)。

  4. 資源管理層:通過槽位(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加鎖,避免多線程競爭。

④deinitinit的反向操作,釋放槽位內存并禁用服務。

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 freedhandle = -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_EVTsdp_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; // 關聯目標服務的 RCBp_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. 提取 UUIDUuid& 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 theentire raw data chunk.TODO: Find a way to split the raw data into record chunks, anditerate to extract generic data for each chunk - e.g. rfcommchannel 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_ptruser2_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 stringmemcpy(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 stringfree_ptr++; // 跳過終止符位置}// 4. 用戶自定義數據深拷貝// 處理 user1_ptrif (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 contentfree_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 pointermemcpy(free_ptr, in_record->hdr.user2_ptr,in_record->hdr.user2_ptr_len);  // Copy contentfree_ptr += in_record->hdr.user2_ptr_len;}}return;
}

將源 SDP 記錄數據深拷貝到目標內存區域,確保目標數據獨立于源數據,避免指針引用導致的內存問題。具體功能包括:

  1. 結構體成員淺拷貝:復制 bluetooth_sdp_record 結構體的基本成員(如服務 UUID、屬性列表指針等)。

  2. 動態數據深拷貝:對結構體中指向動態分配內存的指針(如服務名稱字符串、user1_ptruser2_ptr)進行內存復制,創建獨立副本。

  3. 連續內存管理:將所有復制后的數據存儲在目標內存的連續區域,通過指針偏移計算確保內存布局正確。

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 UUIDsSDP_TYPE_MAP_MAS,     // Message Access Profile - ServerSDP_TYPE_MAP_MNS,     // Message Access Profile - Client (Notification Server)SDP_TYPE_PBAP_PSE,    // Phone Book Profile - ServerSDP_TYPE_PBAP_PCE,    // Phone Book Profile - ClientSDP_TYPE_OPP_SERVER,  // Object Push ProfileSDP_TYPE_SAP_SERVER,  // SIM Access ProfileSDP_TYPE_DIP,         // Device Identification ProfileSDP_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 portuuid = 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 19handle = 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 interfacehandle = 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_AddSequenceuint8_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. 輸出成功日志并寫入 EIRlog::verbose("service registered successfully, service_name: {}, handle: 0x{:08x}",name, handle);{// Write the custom 128-bit UUID to EIRtBTA_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;elsehandle = 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);} elselog::error("SDP_CreateRecord fail, exceed maximum records:{}",SDP_MAX_RECORDS);return (0);
}

用于創建一個空的 SDP 記錄。其主要流程包括:

  1. 記錄池管理:檢查是否有可用記錄槽位(不超過 SDP_MAX_RECORDS)。

  2. 句柄生成:為新記錄分配唯一的句柄(Handle),基于前一條記錄句柄遞增或從初始值 0x10000 開始。

  3. 初始化記錄:清空記錄結構體,自動添加ATTR_ID_SERVICE_RECORD_HDL屬性(存儲句柄值)。

  4. 返回句柄:成功時返回新記錄句柄,失敗時返回 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_LENfor (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 upif (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 記錄中添加或更新屬性。其核心職責包括:

  1. 屬性唯一性管理:若屬性已存在,覆蓋原有值;若不存在,新增屬性并按 ID 排序存儲。

  2. 數據格式校驗:確保屬性值以大端格式(Big Endian)傳遞,符合 SDP 協議規范,并對不同類型屬性(如 UUID、文本、布爾值等)進行差異化日志處理。

  3. 數據庫遍歷與操作:通過記錄句柄查找目標記錄,調用底層函數 SDP_AddAttributeToRecord 完成屬性的實際存儲。

  4. 內存安全控制:檢查記錄剩余空間(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';} elseattr_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;} elselog::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_EVTon_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 的服務記錄。其核心流程包括:

  1. 參數校驗:檢查記錄 ID 是否在有效范圍內。

  2. 線程安全訪問:通過互斥鎖確保多線程環境下對 SDP 槽位的安全操作。

  3. 服務類型識別:根據記錄類型獲取對應的服務 ID,以便禁用相關服務。

  4. 資源釋放:釋放 SDP 槽位,并通過 BTA 接口觸發記錄刪除。

  5. 狀態同步:更新服務啟用狀態,確保協議棧與上層邏輯一致。

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 記錄的接口。其核心作用是將刪除記錄的請求提交到主線程處理,并通過回調機制通知上層邏輯操作結果。具體流程包括:

  1. 異步處理封裝:通過 do_in_main_thread 將刪除操作派發到主線程執行,避免在當前線程阻塞。

  2. 回調觸發:當刪除操作完成后,上層注冊的回調函數(如 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 記錄。其核心邏輯包括:

  1. 全量刪除處理:當傳入句柄為 0 時,清空整個數據庫。

  2. 單記錄刪除處理:根據句柄查找并刪除指定記錄,重組記錄數組以保持連續性。

  3. 元數據更新:更新數據庫記錄數量、主設備信息(DI)句柄等狀態。

三、時序圖

3.1 SDP 服務初始化流程時序圖

3.2 SDP 服務搜索流程時序圖

3.3 創建SDP記錄流程時序圖

3.4 刪除SDP記錄流程時序圖

四、總結

本文圍繞藍牙 SDP 服務記錄的生命周期,詳細解析了從初始化、創建、搜索到刪除的完整流程。核心設計包括:

  • 線程安全:通過主線程任務派發與互斥鎖確保操作原子性。

  • 解耦與抽象:通過接口結構體(sdp_if)和回調機制分離底層實現與上層邏輯。

  • 協議兼容性:支持舊版協議棧接口(get_legacy_stack_sdp_api),并通過類型枚舉(bluetooth_sdp_types)適配多種服務類型。

  • 內存管理:深拷貝與動態內存分配確保數據獨立,避免懸垂指針。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79981.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79981.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79981.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【實戰項目】簡易版的 QQ 音樂:一

> 作者&#xff1a;?舊言~ > 座右銘&#xff1a;松樹千年終是朽&#xff0c;槿花一日自為榮。 > 目標&#xff1a;能自我實現簡易版的 QQ 音樂。 > 毒雞湯&#xff1a;有些事情&#xff0c;總是不明白&#xff0c;所以我不會堅持。早安! > 專欄選自&#xff1a…

Linux_進程退出與進程等待

一、進程退出 ?退出場景? ?正常終止?&#xff1a;代碼執行完畢且結果符合預期&#xff08;退出碼為 0&#xff09;。?異常終止?&#xff1a;運行結果錯誤&#xff08;退出碼非 0&#xff09;或進程被信號強制終止。&#xff08;如 SIGINT 或 SIGSEGV&#xff09;。 ?退…

GD32F407單片機開發入門(二十八)USB口介紹及CDC類虛擬串口通訊詳解及源碼

文章目錄 一.概要二.USB2.0基本介紹及虛擬串口介紹三.GD32單片機USB模塊框圖四.GD32單片機USB設備模式五.GD32F407VET6 USB設備CDC類六.配置一個USB虛擬串口收發例程七.工程源代碼下載八.小結 一.概要 GD32F407VET6USB虛擬串口是一種采用GD32F407VET6單片機&#xff0c;通過US…

MySQL 主從配置超詳細教程

文章目錄 前言一、安裝 MySQL二、主服務器&#xff08;Master&#xff09;配置三、從服務器&#xff08;Slave&#xff09;配置四、測試主從復制五、注意事項 前言 MySQL 主從配置是一種實用的數據庫架構&#xff0c;主服務器處理寫入操作&#xff0c;從服務器負責只讀操作&am…

Python爬蟲實戰:獲取百度學術專題文獻數據并分析,為讀者課題研究做參考

一、引言 在信息爆炸的當下,學術研究需要大量相關資料支撐。百度學術作為重要學術資源平臺,蘊含豐富學術文獻。利用爬蟲技術獲取百度學術特定主題文章數據,能為學術研究提供全面、及時信息。本研究旨在用 Python 實現對百度學術 “主題爬蟲” 相關文章的爬取,并對數據深入…

手撕基于AMQP協議的簡易消息隊列-6(服務端模塊的編寫)

在MQServer中編寫服務端模塊代碼 在MQServer中編寫makefile文件來編譯服務端模塊 .PHONY: server CFLAG -I../ThirdLib/lib/include LFLAG -L../ThirdLib/lib/lib -lgtest -lprotobuf -lsqlite3 -pthread -lmuduo_net -lmuduo_base -lz server:server.cpp ../MQCommon/messag…

linux tar命令詳解。壓縮格式對比

1.壓縮格式對比 壓縮格式命令選項文件擴展名壓縮率速度無壓縮-cvf.tar無最快gzip-czvf.tar.gz中等較快bzip2-cjvf.tar.bz2較高較慢xz-cJvf.tar.xz最高最慢 9. 更多參考 【Linux基礎】文件壓縮tar命令指南tar壓縮方式對比

解鎖跨平臺開發的新時代——Compose Multiplatform

解鎖跨平臺開發的新時代——Compose Multiplatform 在當今移動和桌面應用程序開發領域,跨平臺解決方案是開發者們夢寐以求的工具。而由JetBrains打造的Compose Multiplatform正是這樣一款現代UI框架,它基于Kotlin技術,為開發者構建高性能且美觀的用戶界面提供了極大的便利和…

【算法學習】遞歸、搜索與回溯算法(二)

算法學習&#xff1a; https://blog.csdn.net/2301_80220607/category_12922080.html?spm1001.2014.3001.5482 前言&#xff1a; 在&#xff08;一&#xff09;中我們挑了幾個經典例題&#xff0c;已經對遞歸、搜索與回溯算法進行了初步講解&#xff0c;今天我們來進一步講解…

HTTP請求與緩存、頁面渲染全流程

文章目錄 前言**1. HTTP請求與緩存處理****緩存機制**? 強緩存&#xff08;Cache-Control / Expires&#xff09;? 協商緩存&#xff08;Last-Modified / ETag&#xff09; **2. 服務器響應與數據解析****3. HTML DOM 構建****4. CSSOM 構建****5. 渲染樹&#xff08;Render …

限流算法學習筆記(一)Go Rate Limiter

文章目錄 1. 背景與概述1.1 什么是速率限制1.2 Go Rate Limiter 的定義與價值 2. 核心思想與設計理念2.1 令牌桶算法的基本原理2.2 惰性評估設計2.3 多種處理策略的平衡2.4 簡單易用的偶發控制 3. 架構設計與組件3.1 整體架構3.2 Limiter 組件3.3 Reservation 組件3.4 Limit 類…

n8n工作流自動化平臺的實操:生成統計圖的兩種方式

1.成果展示 1.1n8n的工作流 牽涉節點&#xff1a;Postgres、Code、QuickChart、Edit Fields、HTTP Request 12.顯示效果 2.實操過程 2.1節點說明 2.1.1Postgres節點&#xff1a; 注&#xff1a;將明細數據進行匯總。 2.1.2code節點&#xff1a; 注&#xff1a;將 查詢的數…

JavaScript中數組和對象不同遍歷方法的順序規則

在JavaScript中&#xff0c;不同遍歷方法的順序規則和適用場景存在顯著差異。以下是主要方法的遍歷順序總結&#xff1a; 一、數組遍歷方法 for循環 ? 嚴格按數組索引順序遍歷&#xff08;0 → length-1&#xff09; ? 支持break和continue中斷循環 ? 性能最優&#xff0c;…

緩存(1):三級緩存

三級緩存是指什么 我們常說的三級緩存如下&#xff1a; CPU三級緩存Spring三級緩存應用架構&#xff08;JVM、分布式緩存、db&#xff09;三級緩存 CPU 基本概念 CPU 的訪問速度每 18 個月就會翻 倍&#xff0c;相當于每年增? 60% 左右&#xff0c;內存的速度當然也會不斷…

Android setContentView()源碼分析

文章目錄 Android setContentView()源碼分析前提setContentView() 源碼分析總結 Android setContentView()源碼分析 前提 Activity 的生命周期與 ActivityThread 相關&#xff0c;調用 startActivity() 時&#xff0c;會調用 ActivityThread#performLaunchActivity()&#xf…

uniapp自定義步驟條(可二開進行調試)

前言 有一個業務需求是需要一個步驟條&#xff0c;但是發現開源的都不太合適&#xff0c;所以就自己寫了一個。 開始 test.vue <template><view class"authenticateRecordDetails_container"><!-- 進度 --><view class"authenticateSte…

22、近端策略優化算法(PPO)論文筆記

近端策略優化算法&#xff08;PPO&#xff09;論文筆記 一、研究背景與目標二、**方法****3.1 策略梯度基礎****3.2 信任區域方法&#xff08;TRPO&#xff09;****3.3 剪切代理目標函數&#xff08;LCLIP&#xff09;****3.4 自適應KL懲罰系數****3.5 算法實現** 三、 L CLIP…

web 自動化之 Selenium 元素定位和瀏覽器操作

文章目錄 一、元素定位的八大方法1、基于 id/name/class/tag_name 定位2、基于 a 標簽元素的鏈接文本定位3、基于xpath定位4、css定位 二、瀏覽器操作1、信息獲取2、 瀏覽器關閉3、 瀏覽器控制 一、元素定位的八大方法 web 自動化測試就是通過代碼對網頁進行測試&#xff0c;在…

前端面經 作用域和作用域鏈

含義&#xff1a;JS中變量生效的區域 分類&#xff1a;全局作用域 或者 局部作用域 局部作用域&#xff1a;函數作用域 和 塊級作用域ES6 全局作用域:在代碼中任何地方都生效 函數中定義函數中生效&#xff0c;函數結束失效 塊級作用域 使用let或const 聲明 作用域鏈:JS查…

【C/C++】RPC與線程間通信:高效設計的關鍵選擇

文章目錄 RPC與線程間通信&#xff1a;高效設計的關鍵選擇1 RPC 的核心用途2 線程間通信的常規方法3 RPC 用于線程間通信的潛在意義4 主要缺點與限制4.1 缺點列表4.2 展開 5 替代方案6 結論 RPC與線程間通信&#xff1a;高效設計的關鍵選擇 在C或分布式系統設計中&#xff0c;…