【Bluedroid】蓝牙 SDP(服务发现协议)模块代码解析与流程梳理

news2025/5/9 17:07:46

本文深入剖析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 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_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; // 关联目标服务的 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_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 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 记录数据深拷贝到目标内存区域,确保目标数据独立于源数据,避免指针引用导致的内存问题。具体功能包括:

  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 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 记录。其主要流程包括:

  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_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 记录中添加或更新属性。其核心职责包括:

  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';
    } 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_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.coloradmin.cn/o/2371644.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中国自动驾驶研发解决方案,第一!

4月28日&#xff0c;IDC《中国汽车云市场(2024下半年)跟踪》报告发布&#xff0c;2024下半年中国汽车云市场整体规模达到65.1亿元人民币&#xff0c;同比增长27.4%。IDC认为&#xff0c;自动驾驶技术深化与生成式AI的发展将为汽车云打开新的成长天花板&#xff0c;推动云计算在…

Kubernetes(k8s)学习笔记(四)--入门基本操作

本文通过kubernetes部署tomcat集群&#xff0c;来学习和掌握kubernetes的一些入门基本操作 前提条件 1.各个节点处于Ready状态&#xff1b; 2.配置好docker镜像库(否则会出现ImagePullBackOff等一些问题)&#xff1b; 3.网络配置正常(否则即使应用发布没问题&#xff0c;浏…

【项目篇之统一硬盘操作】仿照RabbitMQ模拟实现消息队列

统一硬盘操作 创建出实例封装交换机的操作封装队列的操作封装绑定的操作封装消息的操作总的完整代码&#xff1a; 我们之前已经使用了数据库去管理交换机&#xff0c;绑定&#xff0c;队列 还使用了数据文件去管理消息 此时我们就搞一个类去把上述两个部分都整合在一起&#…

基于 GO 语言的 Ebyte 勒索软件——简要分析

一种新的勒索软件变种,采用Go 语言编写,使用ChaCha20进行加密,并使用ECIES进行安全密钥传输,加密用户数据并修改系统壁纸。其开发者EvilByteCode曾开发过多种攻击性安全工具,现已在 GitHub 上公开 EByte 勒索软件。尽管该勒索软件声称仅用于教育目的,但滥用可能会导致严重…

0基础 | STM32 | STM32F103C8T6开发板 | 项目开发

注&#xff1a;本专题系列基于该开发板进行&#xff0c;会分享源代码 F103C8T6核心板链接&#xff1a; https://pan.baidu.com/s/1EJOlrTcProNQQhdTT_ayUQ 提取码&#xff1a;8c1w 图 STM32F103C8T6开发板 1、黑色制版工艺、漂亮、高品质 2、入门级配置STM32芯片(SEM32F103…

南京大学OpenHarmony技术俱乐部正式揭牌 仓颉编程语言引领生态创新

2025年4月24日&#xff0c;由OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;项目群技术指导委员会与南京大学软件学院共同举办的“南京大学OpenHarmony技术俱乐部成立大会暨基础软件与生态应用论坛”在南京大学仙林校区召开。 大会聚焦国产自主编程语言…

主场景 工具栏 植物卡牌的渲染

前置知识&#xff1a;使用easyx图形库 1.IMAGE内存变量存储的是一张位图(图像)&#xff0c;存储了像素数据(颜色&#xff0c;尺寸等) 2.loadimage(&变量名&#xff0c;"加载的文件路径")表示从文件中加载图像到变量中 3. saveimage("文件路径", &变…

Java三大基本特征之多态

多态&#xff08;Polymorphism&#xff09;是面向对象编程&#xff08;OOP&#xff09;的三大特性之一&#xff08;另外两个是 封装 和 继承&#xff09;&#xff0c;它允许 同一个行为具有不同的表现形式。在 Java 中&#xff0c;多态主要通过 方法重写&#xff08;Override&a…

OpenCV 基于生物视觉模型的工具------模拟人眼视网膜的生物视觉机制类cv::bioinspired::Retina

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::bioinspired::Retina 是 OpenCV 中用于仿生视觉处理的一个类&#xff0c;它基于生物视觉模型进行图像预处理。该算法特别适用于动态范围调整…

前端跨域问题怎么在后端解决

目录 简单的解决方法&#xff1a; 添加配置类&#xff1a; 为什么会跨域 1. 什么是源 2. URL结构 3. 同源不同源举&#x1f330; 同源例子 不同源例子 4. 浏览器为什么需要同源策略 5. 常规前端请求跨域 简单的解决方法&#xff1a; 添加配置类&#xff1a; packag…

Python小程序:上班该做点摸鱼的事情

系统提醒 上班会忘记一些自己的事&#xff0c;所以你需要在上班的的时候突然给你弹窗&#xff0c;你就知道要做啥了 源码 这里有一个智能家居项目可以看看(开源) # -*- coding:utf-8 -*- """ 作者:YTQ 日期: 2025年04日29 21:51:24 """ impor…

飞云分仓操盘副图指标操作技术图文分解

如上图&#xff0c;副图指标-飞云分仓操盘指标&#xff0c;指标三条线蓝色“首峰线”&#xff0c;红色“引力1”&#xff0c;青色“引力2”&#xff0c;多头行情时“首峰线”和“引力1”之间显示为红色&#xff0c;“引力1”和“引力2”多头是区间颜色显示为紫色。 如上图图标信…

基于vueflow可拖拽元素的示例(基于官网示例的单文件示例)

效果图 代码 <template><div style"width: 100%;height: calc(100vh - 84px)"><VueFlow :nodes"nodes" :edges"edges" drop"onDrop" dragover"onDragOver" dragleave"onDragLeave"><div cl…

【MongoDB篇】MongoDB的副本集操作!

目录 引言第一节&#xff1a;副本集的核心概念&#xff1a;它是什么&#xff1f;为什么需要它&#xff1f;&#x1f914;&#x1f9e0;第二节&#xff1a;副本集的“骨架”&#xff1a;成员与数据同步机制 &#x1f451;&#x1f504;❤️‍&#x1f525;第三节&#xff1a;生死…

Kubernetes 集群优化实战手册:从零到生产级性能调优

一、硬件资源优化策略 1. 节点选型黄金法则 # 生产环境常见节点规格&#xff08;AWS示例&#xff09; - 常规计算型&#xff1a;m5.xlarge (4vCPU 16GB) - 内存优化型&#xff1a;r5.2xlarge (8vCPU 64GB) - GPU加速型&#xff1a;p3.2xlarge (8vCPU V100 GPU)2. 自动扩缩容…

【Redis分布式】主从复制

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【中间件】企业级中间件剖析 一、主从复制 在分布式系统之中为了解决单点问题&#xff08;1、可用性问题&#xff0c;该机器挂掉服务会停止2、性能支持的并发量是有限的&#xff09;通常会把数据复制多…

用递归实现各种排列

为了满足字典序的输出&#xff0c;我采用了逐位递归的方法&#xff08;每一位的所能取到的最小值都大于前一位&#xff09; 1&#xff0c;指数型排列 #include<bits/stdc.h> using ll long long int; using namespace std; int a[10];void printp(int m) {for (int h …

测试用例介绍

文章目录 一、测试用例基本概念1.1 测试用例基本要素 二、测试用例的设计方法2.1 基于需求的设计方法2.2 等价类2.3 边界值2.4 错误猜测法2.6 场景设计法2.7 因果图2.5 正交排列 三、综合&#xff1a;根据某个场景去设计测试用例&#xff08;万能公式&#xff09;四、如何使用F…

phpstudy升级新版apache

1.首先下载要升级到的apache版本&#xff0c;这里apache版本为Apache 2.4.63-250207 Win64下载地址&#xff1a;Apache VS17 binaries and modules download 2.将phpstudy中原始apache复制备份Apache2.4.39_origin 3.将1中下载apache解压&#xff0c; 将Apache24复制一份到ph…

React Native基础环境配置

React Native基础环境配置 1.引言2.React-Native简介3.项目基础环境搭建1.引言 感觉自己掌握的知识面还是有点太窄了,于是决定看看移动端的框架,搞个react搭一个后端管理项目,然后拿react-native写个小的软件,试着找个三方上架一下应用市场玩玩。毕竟不可能一直在简历上挂一…