Merge branch 'idf/ble_audio_pts_v6.1' into 'release/v6.1'

feat(ble_audio): Miscellaneous update for verifying with PTS cases (v6.1)

See merge request espressif/esp-idf!51421
This commit is contained in:
Jiang Jiang Jian
2026-08-31 11:28:03 +08:00
80 changed files with 2751 additions and 422 deletions
@@ -112,6 +112,12 @@ if(CONFIG_BT_TMAP)
)
endif()
if(CONFIG_BT_GMAP)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/gmas.c"
)
endif()
if(CONFIG_BT_VCP_VOL_REND)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/${audio_adapter}/profiles/vcs.c"
@@ -55,6 +55,12 @@ if BT_CSIP_SET_MEMBER
help
This option enables support for clients to be notified on Set Size changes.
config BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE
bool "Coordinated Set Name notifiable support"
help
This option enables support for clients to be notified on Coordinated Set Name
changes (CSIS v1.1 Coordinated Set Name characteristic).
endif # BT_CSIP_SET_MEMBER
#################### Coordinated Set Identification Client ####################
@@ -14,6 +14,16 @@ config BT_MCS
help
This option enables support for the Media Control Service.
config BT_MCS_INSTANCE_COUNT
int "How many discrete MCS (0x1848) instances the device instantiates"
depends on BT_MCS
default 0
range 0 255
help
Number of discrete Media Control Service instances (0x1848), each bound
to its own media player. GMCS (0x1849) is always present in addition to
these. Leave at 0 for a GMCS-only device.
#### Media Control Client ################################
config BT_MCC
@@ -80,10 +80,11 @@ esp_err_t esp_ble_audio_bap_unicast_server_unregister_cb(const esp_ble_audio_bap
return ESP_OK;
}
esp_err_t esp_ble_audio_bap_unicast_server_config_ase(uint16_t conn_handle,
esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_codec_cfg_t *codec_cfg,
const esp_ble_audio_bap_qos_cfg_pref_t *qos_pref)
esp_err_t esp_ble_audio_bap_unicast_server_config_ase_with_dir(uint16_t conn_handle,
esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_codec_cfg_t *codec_cfg,
const esp_ble_audio_bap_qos_cfg_pref_t *qos_pref,
esp_ble_audio_dir_t dir)
{
esp_err_t ret = ESP_OK;
void *conn;
@@ -101,7 +102,7 @@ esp_err_t esp_ble_audio_bap_unicast_server_config_ase(uint16_t conn_handle,
goto unlock;
}
err = bt_bap_unicast_server_config_ase(conn, stream, codec_cfg, qos_pref);
err = bt_bap_unicast_server_config_ase_with_dir(conn, stream, codec_cfg, qos_pref, dir);
if (err) {
ret = ESP_FAIL;
}
@@ -110,6 +111,16 @@ unlock:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_bap_unicast_server_config_ase(uint16_t conn_handle,
esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_codec_cfg_t *codec_cfg,
const esp_ble_audio_bap_qos_cfg_pref_t *qos_pref)
{
/* Direction-agnostic: dir 0 lets the library pick the first free ASE. */
return esp_ble_audio_bap_unicast_server_config_ase_with_dir(conn_handle, stream, codec_cfg,
qos_pref, (esp_ble_audio_dir_t)0);
}
#endif /* CONFIG_BT_BAP_UNICAST_SERVER */
#if CONFIG_BT_BAP_UNICAST_CLIENT
@@ -465,18 +465,32 @@ esp_err_t esp_ble_audio_cap_handover_unregister_cb(const esp_ble_audio_cap_hando
esp_err_t esp_ble_audio_cap_handover_unicast_to_broadcast(
const esp_ble_audio_cap_handover_unicast_to_broadcast_param_t *param)
{
esp_ble_audio_cap_handover_unicast_to_broadcast_param_t p;
esp_err_t ret = ESP_OK;
int err;
if (param == NULL) {
return ESP_ERR_INVALID_ARG;
}
err = bt_cap_handover_unicast_to_broadcast_safe(param);
if (err) {
return ESP_FAIL;
p = *param;
bt_le_host_lock();
p.ext_adv = bt_le_ext_adv_find(p.adv_handle);
if (p.ext_adv == NULL) {
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
return ESP_OK;
err = bt_cap_handover_unicast_to_broadcast(&p);
if (err) {
ret = ESP_FAIL;
}
unlock:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_cap_handover_broadcast_to_unicast(
@@ -101,6 +101,24 @@ esp_err_t esp_ble_audio_csip_set_member_set_size_and_rank(esp_ble_audio_csip_set
return ESP_OK;
}
esp_err_t esp_ble_audio_csip_set_member_set_name(esp_ble_audio_csip_set_member_svc_inst_t *svc_inst,
const uint8_t *name, uint8_t len)
{
int err;
if (svc_inst == NULL || len > ESP_BLE_AUDIO_CSIP_SET_NAME_MAX_LEN ||
(len > 0 && name == NULL)) {
return ESP_ERR_INVALID_ARG;
}
err = bt_csip_set_member_set_name_safe(svc_inst, name, len);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_ble_audio_csip_set_member_get_info(const esp_ble_audio_csip_set_member_svc_inst_t *svc_inst,
esp_ble_audio_csip_set_member_set_info_t *info)
{
@@ -86,6 +86,28 @@ esp_err_t esp_ble_audio_has_client_preset_set(esp_ble_audio_has_t *has,
return ESP_OK;
}
esp_err_t esp_ble_audio_has_client_preset_name_write(esp_ble_audio_has_t *has,
uint8_t index, const char *name)
{
int err;
if (has == NULL || index == ESP_BLE_AUDIO_HAS_PRESET_INDEX_NONE || name == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (strlen(name) < ESP_BLE_AUDIO_HAS_PRESET_NAME_MIN ||
strlen(name) > ESP_BLE_AUDIO_HAS_PRESET_NAME_MAX) {
return ESP_ERR_INVALID_ARG;
}
err = bt_has_client_preset_name_write_safe(has, index, name);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_ble_audio_has_client_preset_next(esp_ble_audio_has_t *has, bool sync)
{
int err;
@@ -228,10 +250,6 @@ esp_err_t esp_ble_audio_has_preset_active_set(uint8_t index)
{
int err;
if (index == ESP_BLE_AUDIO_HAS_PRESET_INDEX_NONE) {
return ESP_ERR_INVALID_ARG;
}
err = bt_has_preset_active_set_safe(index);
if (err) {
return ESP_FAIL;
@@ -5,6 +5,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_ble_audio_mcs_defs.h"
#include "esp_ble_audio_media_proxy_api.h"
@@ -578,6 +580,38 @@ esp_err_t esp_ble_audio_media_proxy_pl_init(void)
return ESP_OK;
}
esp_err_t esp_ble_audio_media_proxy_pl_set_player_name(char *name)
{
int err;
if (name == NULL || strlen(name) > CONFIG_BT_MPL_MEDIA_PLAYER_NAME_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
err = bt_media_proxy_pl_set_player_name_safe(name);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_ble_audio_media_proxy_pl_set_track_title(char *title)
{
int err;
if (title == NULL || strlen(title) > CONFIG_BT_MPL_TRACK_TITLE_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
err = bt_media_proxy_pl_set_track_title_safe(title);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
#endif /* CONFIG_BT_MCTL_LOCAL_PLAYER_CONTROL */
#if CONFIG_BT_MCS && CONFIG_BT_OTS
@@ -162,6 +162,47 @@ esp_err_t esp_ble_audio_tbs_remote_incoming(uint8_t bearer_index,
return ESP_OK;
}
esp_err_t esp_ble_audio_tbs_add_call(uint8_t bearer_index, uint8_t state,
const char *uri, uint8_t *call_index)
{
int ret;
if (uri == NULL || call_index == NULL) {
return ESP_ERR_INVALID_ARG;
}
ret = bt_tbs_add_call_safe(bearer_index, state, uri, call_index);
if (ret < 0) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_ble_audio_tbs_set_auto_alerting(uint8_t bearer_index, bool enable)
{
int err;
err = bt_tbs_set_auto_alerting_safe(bearer_index, enable);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_ble_audio_tbs_set_call_alerting(uint8_t call_index)
{
int err;
err = bt_tbs_set_call_alerting_safe(call_index);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_ble_audio_tbs_set_bearer_provider_name(uint8_t bearer_index, const char *name)
{
int err;
@@ -233,10 +274,9 @@ esp_err_t esp_ble_audio_tbs_set_status_flags(uint8_t bearer_index, uint16_t stat
esp_err_t esp_ble_audio_tbs_set_uri_scheme_list(uint8_t bearer_index, const char *uri_scheme_list)
{
uint8_t count = CONFIG_BT_TBS_BEARER_COUNT;
int err;
if (count == 0 || bearer_index >= count || uri_scheme_list == NULL) {
if (uri_scheme_list == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -303,11 +343,6 @@ esp_err_t esp_ble_audio_tbs_register_bearer(const esp_ble_audio_tbs_register_par
return ESP_ERR_INVALID_ARG;
}
/* Note: currently only GTBS is supported */
if (param->gtbs == false) {
return ESP_ERR_INVALID_ARG;
}
ret = bt_tbs_register_bearer_safe(param);
if (ret < 0) {
return ESP_FAIL;
@@ -316,7 +351,7 @@ esp_err_t esp_ble_audio_tbs_register_bearer(const esp_ble_audio_tbs_register_par
*bearer_index = ret;
#if BLE_AUDIO_SVC_DEFERRED_ADD
if (bt_le_gtbs_init()) {
if (param->gtbs ? bt_le_gtbs_init() : bt_le_tbs_init()) {
bt_tbs_unregister_bearer_safe(*bearer_index);
return ESP_FAIL;
}
@@ -185,6 +185,22 @@ esp_err_t esp_ble_audio_vcp_vol_rend_mute(void)
return ESP_OK;
}
esp_err_t esp_ble_audio_vcp_vol_rend_reset_state(const esp_ble_audio_vcp_vol_rend_reset_state_param_t *param)
{
int err;
if (param == NULL) {
return ESP_ERR_INVALID_ARG;
}
err = bt_vcp_vol_rend_reset_state_safe(param);
if (err) {
return ESP_FAIL;
}
return ESP_OK;
}
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_VCP_VOL_CTLR
@@ -395,6 +395,12 @@ esp_err_t esp_ble_audio_bap_unicast_server_unregister_cb(const esp_ble_audio_bap
/**
* @brief Initialize and configure a new ASE.
*
* @note Direction-agnostic: configures the first free ASE by index. Sink ASEs
* are ordered before source ASEs, so this picks a sink ASE whenever one
* is free and only falls back to a source ASE when all sink ASEs are in
* use. Use esp_ble_audio_bap_unicast_server_config_ase_with_dir() to
* target a specific direction.
*
* @param conn_handle Connection handle.
* @param stream Configured stream object to be attached to the ASE.
* @param codec_cfg Codec configuration.
@@ -407,6 +413,27 @@ esp_err_t esp_ble_audio_bap_unicast_server_config_ase(uint16_t conn_handle,
esp_ble_audio_codec_cfg_t *codec_cfg,
const esp_ble_audio_bap_qos_cfg_pref_t *qos_pref);
/**
* @brief Initialize and configure a new ASE of a specific direction.
*
* Like esp_ble_audio_bap_unicast_server_config_ase(), but configures the first
* free ASE of the requested direction, letting a server initiate a config on a
* source ASE (the direction-agnostic variant picks the first free ASE).
*
* @param conn_handle Connection handle.
* @param stream Configured stream object to be attached to the ASE.
* @param codec_cfg Codec configuration.
* @param qos_pref Audio Stream Quality of Service Preference.
* @param dir ASE direction to configure (sink or source).
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_bap_unicast_server_config_ase_with_dir(uint16_t conn_handle,
esp_ble_audio_bap_stream_t *stream,
esp_ble_audio_codec_cfg_t *codec_cfg,
const esp_ble_audio_bap_qos_cfg_pref_t *qos_pref,
esp_ble_audio_dir_t dir);
/**
* @brief Create unicast group.
*
@@ -26,7 +26,38 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
BT_BAP_LC3_PRESET(_codec, _qos)
/**
* @brief Define an LC3 preset configuration.
* @brief Define an LC3 preset from a pre-built QoS configuration.
*
* @param _name Preset configuration name.
* @param _freq Sample frequency (in Hz).
* @param _duration Frame duration (in microseconds).
* @param _loc Audio channel location.
* @param _len Frame length in octets.
* @param _frames_per_sdu Number of frames per SDU.
* @param _stream_context Stream context (e.g., Media, Conversational, etc.).
* @param _qos BAP QoS configuration (framed or unframed).
*
* Backing buffers are sized to `CONFIG_BT_AUDIO_CODEC_CFG_MAX_*_SIZE` so
* the preset is safe to mutate via `esp_ble_audio_codec_cfg_*_set_*`.
*/
#define ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_QOS(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, _qos) \
static uint8_t codec_cfg_data_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, _len, _frames_per_sdu); \
static uint8_t codec_cfg_meta_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context); \
static esp_ble_audio_bap_lc3_preset_t _name = \
ESP_BLE_AUDIO_BAP_LC3_PRESET( \
ESP_BLE_AUDIO_CODEC_CFG_LC3_LEN( \
codec_cfg_data_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, \
_len, _frames_per_sdu)), \
codec_cfg_meta_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context))), \
_qos);
/**
* @brief Define an LC3 preset configuration with unframed QoS.
*
* @param _name Preset configuration name.
* @param _freq Sample frequency (in Hz).
@@ -40,26 +71,41 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _rtn Number of retransmissions.
* @param _latency Maximum transport latency (in milliseconds).
* @param _pd Presentation delay (in microseconds).
*
* Backing buffers are sized to `CONFIG_BT_AUDIO_CODEC_CFG_MAX_*_SIZE` so
* the preset is safe to mutate via `esp_ble_audio_codec_cfg_*_set_*`.
*/
#define ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, \
_interval, _sdu, _rtn, _latency, _pd) \
static uint8_t codec_cfg_data_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, _len, _frames_per_sdu); \
static uint8_t codec_cfg_meta_##_name[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE] = \
ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context); \
static esp_ble_audio_bap_lc3_preset_t _name = \
ESP_BLE_AUDIO_BAP_LC3_PRESET( \
ESP_BLE_AUDIO_CODEC_CFG_LC3_LEN( \
codec_cfg_data_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, \
_len, _frames_per_sdu)), \
codec_cfg_meta_##_name, \
sizeof((uint8_t[])ESP_BLE_AUDIO_CODEC_CFG_LC3_META(_stream_context))), \
ESP_BLE_AUDIO_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, _rtn, _latency, _pd));
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_QOS(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, \
ESP_BLE_AUDIO_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, \
_rtn, _latency, _pd))
/**
* @brief Define an LC3 preset configuration with framed QoS.
*
* Required for 44.1 kHz configs: their SDU interval is not an integer number of
* microseconds, so unframed PDUs are invalid (BAP spec Table 5.2).
*
* @param _name Preset configuration name.
* @param _freq Sample frequency (in Hz).
* @param _duration Frame duration (in microseconds).
* @param _loc Audio channel location.
* @param _len Frame length in octets.
* @param _frames_per_sdu Number of frames per SDU.
* @param _stream_context Stream context (e.g., Media, Conversational, etc.).
* @param _interval Audio frame interval (in microseconds).
* @param _sdu SDU size in octets.
* @param _rtn Number of retransmissions.
* @param _latency Maximum transport latency (in milliseconds).
* @param _pd Presentation delay (in microseconds).
*/
#define ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, \
_interval, _sdu, _rtn, _latency, _pd) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_QOS(_name, _freq, _duration, _loc, _len, \
_frames_per_sdu, _stream_context, \
ESP_BLE_AUDIO_BAP_QOS_CFG_FRAMED(_interval, _sdu, \
_rtn, _latency, _pd))
/**
* @brief Helper to declare LC3 Unicast 8_1_1 codec configuration.
@@ -191,12 +237,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_UNICAST_PRESET_441_1_1_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 5u, 24u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 5u, 24u, 40000u)
/**
* @brief Helper to declare LC3 Unicast 441_2_1 codec configuration.
@@ -206,12 +252,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_UNICAST_PRESET_441_2_1_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 5u, 31u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 5u, 31u, 40000u)
/**
* @brief Helper to declare LC3 Unicast 48_1_1 codec configuration.
@@ -431,12 +477,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_UNICAST_PRESET_441_1_2_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 13u, 80u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 13u, 80u, 40000u)
/**
* @brief Helper to declare LC3 Unicast 441_2_2 codec configuration.
@@ -446,12 +492,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_UNICAST_PRESET_441_2_2_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 13u, 85u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 13u, 85u, 40000u)
/**
* @brief Helper to declare LC3 Unicast 48_1_2 codec configuration.
@@ -675,12 +721,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_BROADCAST_PRESET_441_1_1_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 4u, 24u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 4u, 24u, 40000u)
/**
* @brief Helper to declare LC3 Broadcast 441_2_1 codec configuration.
@@ -690,12 +736,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_BROADCAST_PRESET_441_2_1_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 4u, 31u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 4u, 31u, 40000u)
/**
* @brief Helper to declare LC3 Broadcast 48_1_1 codec configuration.
@@ -919,12 +965,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_BROADCAST_PRESET_441_1_2_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 4u, 54u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_7_5, \
_loc, 97U, 1, \
_stream_context, \
8163u, 97u, 4u, 54u, 40000u)
/**
* @brief Helper to declare LC3 Broadcast 441_2_2 codec configuration.
@@ -934,12 +980,12 @@ typedef struct bt_bap_lc3_preset esp_ble_audio_bap_lc3_preset_t;
* @param _stream_context Stream context.
*/
#define ESP_BLE_AUDIO_BAP_LC3_BROADCAST_PRESET_441_2_2_DEFINE(_name, _loc, _stream_context) \
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 4u, 60u, 40000u)
ESP_BLE_AUDIO_BAP_LC3_PRESET_DEFINE_FRAMED(_name, \
BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \
BT_AUDIO_CODEC_CFG_DURATION_10, \
_loc, 130U, 1, \
_stream_context, \
10884u, 130u, 4u, 60u, 40000u)
/**
* @brief Helper to declare LC3 Broadcast 48_1_2 codec configuration.
@@ -498,7 +498,9 @@ esp_err_t esp_ble_audio_cap_handover_unregister_cb(const esp_ble_audio_cap_hando
* esp_ble_audio_bap_broadcast_assistant_discover() must have been successfully perform for
* all members in @p param before starting this procedure.
*
* @param param The parameters for the handover.
* @param param The parameters for the handover. Its @c ext_adv field is resolved
* internally from @c param->adv_handle, so the caller sets adv_handle
* and leaves ext_adv NULL.
*
* @return ESP_OK on success, or an error code on failure.
*/
@@ -26,10 +26,13 @@ extern "C" {
#endif
/*!< Size of the Set Identification Resolving Key (SIRK) */
#define ESP_BLE_AUDIO_CSIP_SIRK_SIZE BT_CSIP_SIRK_SIZE
#define ESP_BLE_AUDIO_CSIP_SIRK_SIZE BT_CSIP_SIRK_SIZE
/*!< Size of the Resolvable Set Identifier (RSI) */
#define ESP_BLE_AUDIO_CSIP_RSI_SIZE BT_CSIP_RSI_SIZE
#define ESP_BLE_AUDIO_CSIP_RSI_SIZE BT_CSIP_RSI_SIZE
/*!< Maximum length of the Coordinated Set Name (CSIS v1.1), a 0-128 octet UTF-8 string */
#define ESP_BLE_AUDIO_CSIP_SET_NAME_MAX_LEN BT_CSIP_SET_NAME_MAX_LEN
/*!< Accept the request to read the SIRK as plaintext */
#define ESP_BLE_AUDIO_CSIP_READ_SIRK_REQ_RSP_ACCEPT BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT
@@ -142,6 +145,21 @@ esp_err_t esp_ble_audio_csip_set_member_sirk(esp_ble_audio_csip_set_member_svc_i
esp_err_t esp_ble_audio_csip_set_member_set_size_and_rank(esp_ble_audio_csip_set_member_svc_inst_t *svc_inst,
uint8_t size, uint8_t rank);
/**
* @brief Set the Coordinated Set Name (CSIS v1.1) for a service instance.
*
* If CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE is enabled, this also notifies subscribed
* clients (the first ATT_MTU-3 octets if the name is longer).
*
* @param svc_inst The service instance.
* @param name The new name (UTF-8). May be NULL only if @p len is 0.
* @param len Length of @p name in octets (0 to BT_CSIP_SET_NAME_MAX_LEN).
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_csip_set_member_set_name(esp_ble_audio_csip_set_member_svc_inst_t *svc_inst,
const uint8_t *name, uint8_t len);
/**
* @brief Get information about a service instances.
*
@@ -341,6 +341,8 @@ typedef enum bt_audio_assisted_listening_stream esp_ble_audio_assist
#define ESP_BLE_AUDIO_UUID_CSIS_SET_LOCK_VAL BT_UUID_CSIS_SET_LOCK_VAL
/*!< Rank value */
#define ESP_BLE_AUDIO_UUID_CSIS_RANK_VAL BT_UUID_CSIS_RANK_VAL
/*!< Coordinated Set Name value */
#define ESP_BLE_AUDIO_UUID_CSIS_SET_NAME_VAL BT_UUID_CSIS_SET_NAME_VAL
/*!< Gaming Audio Profile Role UUID value */
#define ESP_BLE_AUDIO_UUID_GMAP_ROLE_VAL BT_UUID_GMAP_ROLE_VAL
@@ -161,6 +161,20 @@ esp_err_t esp_ble_audio_has_client_preset_next(esp_ble_audio_has_t *has, bool sy
*/
esp_err_t esp_ble_audio_has_client_preset_prev(esp_ble_audio_has_t *has, bool sync);
/**
* @brief Write Preset Name.
*
* Client procedure to change the name of the preset identified by @p index.
*
* @param has Pointer to the Hearing Access Service object.
* @param index Preset index whose name to change.
* @param name New preset name (1..40 octets).
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_has_client_preset_name_write(esp_ble_audio_has_t *has,
uint8_t index, const char *name);
/**
* @brief Register the Hearing Access Service instance.
*
@@ -729,6 +729,24 @@ esp_err_t esp_ble_audio_media_proxy_pl_register(esp_ble_audio_media_proxy_pl_cal
*/
esp_err_t esp_ble_audio_media_proxy_pl_init(void);
/**
* @brief Set the local player name and notify it.
*
* @param name New player name. Copied internally; may be freed after the call returns.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_media_proxy_pl_set_player_name(char *name);
/**
* @brief Set the current track title and notify it.
*
* @param title New track title. Copied internally; may be freed after the call returns.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_media_proxy_pl_set_track_title(char *title);
/**
* @brief Get the pointer of the Object Transfer Service used by the Media Control Service.
*
@@ -271,6 +271,47 @@ esp_err_t esp_ble_audio_tbs_remote_incoming(uint8_t bearer_index,
const char *friendly_name,
uint8_t *call_index);
/**
* @brief Create a call directly in a given state (test setup helper).
*
* Allocates a call and sets it to @p state without running the call state
* machine, bypassing the single-outgoing-call restriction and the
* Dialing->Alerting auto-promotion. Intended for setting up the fixed call
* configurations required by the Join test procedures.
*
* @param bearer_index The index of the Telephone Bearer.
* @param state The initial call state (ESP_BLE_AUDIO_TBS_CALL_STATE_*).
* @param uri The remote URI stored for the call.
* @param call_index The call index on success.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_tbs_add_call(uint8_t bearer_index, uint8_t state,
const char *uri, uint8_t *call_index);
/**
* @brief Enable/disable automatic Dialing->Alerting promotion (test control).
*
* When disabled, an originated call on the bearer stays in Dialing until
* esp_ble_audio_tbs_set_call_alerting() is called. Lets a test harness control
* the transition timing and hold several outgoing calls in Dialing at once.
*
* @param bearer_index The index of the Telephone Bearer or GTBS index.
* @param enable true to auto-promote (default), false to keep Dialing.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_tbs_set_auto_alerting(uint8_t bearer_index, bool enable);
/**
* @brief Move a Dialing call to the Alerting state (test setup helper).
*
* @param call_index The call index to promote.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_tbs_set_call_alerting(uint8_t call_index);
/**
* @brief Set a new bearer provider.
*
@@ -29,6 +29,11 @@ extern "C" {
/*!< The volume state is muted */
#define ESP_BLE_AUDIO_VCP_STATE_MUTED BT_VCP_STATE_MUTED
/*!< Reset selector: apply the volume field (clears persisted flag) */
#define ESP_BLE_AUDIO_VCP_RESET_VOLUME BT_VCP_VOL_REND_RESET_VOLUME
/*!< Reset selector: apply the mute field */
#define ESP_BLE_AUDIO_VCP_RESET_MUTE BT_VCP_VOL_REND_RESET_MUTE
/** Volume Control Service included services */
typedef struct bt_vcp_included esp_ble_audio_vcp_included_t;
@@ -44,6 +49,9 @@ typedef struct bt_vcp_vol_rend_cb esp_ble_audio_vcp_vol_rend_cb_t;
/** Register structure for Volume Control Service */
typedef struct bt_vcp_vol_rend_register_param esp_ble_audio_vcp_vol_rend_register_param_t;
/** Parameters for resetting the Volume Renderer state */
typedef struct bt_vcp_vol_rend_reset_param esp_ble_audio_vcp_vol_rend_reset_state_param_t;
/**
* @brief Get Volume Control Service included services.
*
@@ -146,6 +154,21 @@ esp_err_t esp_ble_audio_vcp_vol_rend_unmute(void);
*/
esp_err_t esp_ble_audio_vcp_vol_rend_mute(void);
/**
* @brief Reset selected volume state fields to an initial/reset value.
*
* For each field selected in @p param->fields (ESP_BLE_AUDIO_VCP_RESET_*), sets
* it to the provided value without treating it as a user change. Resetting the
* volume returns Volume_Setting_Persisted to Reset Volume Setting (0) per VCS
* v1.0.1 Section 3.3.1. Use to establish a starting state that must report
* persisted = 0.
*
* @param param Reset parameters. Must not be NULL.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t esp_ble_audio_vcp_vol_rend_reset_state(const esp_ble_audio_vcp_vol_rend_reset_state_param_t *param);
/**
* @brief Registers the callbacks used by the Volume Controller.
*
@@ -19,6 +19,7 @@
#include "bluedroid/profiles/pacs.h"
#include "bluedroid/profiles/tbs.h"
#include "bluedroid/profiles/tmas.h"
#include "bluedroid/profiles/gmas.h"
#include "bluedroid/profiles/vcs.h"
#include "bluedroid/profiles/has.h"
@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_BLUEDROID_PROFILE_GMAS_H_
#define HOST_BLUEDROID_PROFILE_GMAS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_bluedroid_gmas_init(void);
int bt_le_bluedroid_gmas_start(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_BLUEDROID_PROFILE_GMAS_H_ */
@@ -18,6 +18,10 @@ int bt_le_bluedroid_gmcs_init(void);
int bt_le_bluedroid_gmcs_start(void);
int bt_le_bluedroid_mcs_init(void);
int bt_le_bluedroid_mcs_start(void);
#ifdef __cplusplus
}
#endif
@@ -18,6 +18,10 @@ int bt_le_bluedroid_gtbs_init(void);
int bt_le_bluedroid_gtbs_start(void);
int bt_le_bluedroid_tbs_init(void);
int bt_le_bluedroid_tbs_start(void);
#ifdef __cplusplus
}
#endif
@@ -76,6 +76,7 @@ static inline char *audio_chrc_uuid_to_str(uint16_t uuid)
case BT_UUID_CSIS_SET_SIZE_VAL: return "CSIS_SET_SIZE";
case BT_UUID_CSIS_SET_LOCK_VAL: return "CSIS_SET_LOCK";
case BT_UUID_CSIS_RANK_VAL: return "CSIS_RANK";
case BT_UUID_CSIS_SET_NAME_VAL: return "CSIS_SET_NAME";
case BT_UUID_MCS_PLAYER_NAME_VAL: return "MCS_PLAYER_NAME";
case BT_UUID_MCS_ICON_OBJ_ID_VAL: return "MCS_ICON_OBJ_ID";
case BT_UUID_MCS_ICON_URL_VAL: return "MCS_ICON_URL";
@@ -153,6 +154,8 @@ enum {
TMAS_IN_PROGRESS,
VCS_IN_PROGRESS,
VOCS_IN_PROGRESS,
GMAS_IN_PROGRESS,
OTS_IN_PROGRESS,
MAX_IN_PROGRESS,
};
@@ -30,13 +30,27 @@ LOG_MODULE_REGISTER(LEA_BINIT, CONFIG_BT_ISO_LOG_LEVEL);
#define CSIS_SVC_COUNT 0
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_TBS
#define TBS_BEARER_SVC_COUNT CONFIG_BT_TBS_BEARER_COUNT
#else /* CONFIG_BT_TBS */
#define TBS_BEARER_SVC_COUNT 0
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_MCS
#define MCS_INSTANCE_SVC_COUNT CONFIG_BT_MCS_INSTANCE_COUNT
#else /* CONFIG_BT_MCS */
#define MCS_INSTANCE_SVC_COUNT 0
#endif /* CONFIG_BT_MCS */
/* 3 is reserved for other GATT services */
#define TOTAL_SERVICE_COUNT (3 + \
(IS_ENABLED(CONFIG_BT_ASCS) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_PACS) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_TMAP) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_GMAP) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_MCS) ? 1 : 0) + \
MCS_INSTANCE_SVC_COUNT + \
(IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER) ? CSIS_SVC_COUNT : 0) + \
(IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR) ? 1 : 0) + \
(IS_ENABLED(CONFIG_BT_VCP_VOL_REND) ? 1 : 0) + \
@@ -44,6 +58,7 @@ LOG_MODULE_REGISTER(LEA_BINIT, CONFIG_BT_ISO_LOG_LEVEL);
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT + \
CONFIG_BT_AICS_MAX_INSTANCE_COUNT + \
(IS_ENABLED(CONFIG_BT_TBS) ? 1 : 0) + \
TBS_BEARER_SVC_COUNT + \
(IS_ENABLED(CONFIG_BT_HAS) ? 1 : 0))
_Static_assert(TOTAL_SERVICE_COUNT <= CONFIG_BT_GATT_MAX_SR_PROFILES, "Too small BT_GATT_MAX_SR_PROFILES");
@@ -90,6 +105,11 @@ int bt_le_bluedroid_audio_init(void)
if (err) {
return err;
}
err = bt_le_bluedroid_tbs_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
@@ -156,15 +176,16 @@ int bt_le_bluedroid_media_proxy_pl_init(void)
{
int err;
#if CONFIG_BT_MPL_OBJECTS
#endif /* CONFIG_BT_MPL_OBJECTS */
err = bt_le_bluedroid_gmcs_init();
if (err) {
return err;
}
err = bt_le_bluedroid_mcs_init();
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_MCS */
@@ -289,6 +310,20 @@ int bt_le_bluedroid_audio_start(void *info)
}
#endif /* CONFIG_BT_TMAP */
#if CONFIG_BT_GMAP
/* GMAS attrs are role-dependent (filled by esp_ble_audio_gmap_register, after
* audio_init), so create it here in the start phase where it's populated. */
err = bt_le_bluedroid_gmas_init();
if (err) {
return err;
}
err = bt_le_bluedroid_gmas_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_GMAP */
#if CONFIG_BT_CSIP_SET_MEMBER
err = bt_le_bluedroid_csis_start();
if (err) {
@@ -322,6 +357,11 @@ int bt_le_bluedroid_audio_start(void *info)
if (err) {
return err;
}
err = bt_le_bluedroid_mcs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_TBS
@@ -329,6 +369,11 @@ int bt_le_bluedroid_audio_start(void *info)
if (err) {
return err;
}
err = bt_le_bluedroid_tbs_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bluedroid/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_GMAS, CONFIG_BT_ISO_LOG_LEVEL);
int bt_le_bluedroid_gmas_init(void)
{
struct bt_gatt_service *gmas_svc;
LOG_DBG("[B]GmasInit");
gmas_svc = lib_gmas_svc_get();
if (!gmas_svc) {
LOG_ERR("[B]GmasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(GMAS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(gmas_svc);
}
int bt_le_bluedroid_gmas_start(void)
{
struct bt_gatt_service *gmas_svc;
LOG_DBG("[B]GmasStart");
gmas_svc = lib_gmas_svc_get();
if (!gmas_svc) {
LOG_ERR("[B]GmasSvcGetFail");
return -ENODEV;
}
bt_le_bluedroid_set_svc_in_progress(GMAS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(gmas_svc);
}
@@ -19,34 +19,164 @@
#include "../../../lib/include/audio.h"
#if CONFIG_BT_OTS
#include "ots_internal.h"
#endif /* CONFIG_BT_OTS */
LOG_MODULE_REGISTER(LEA_MCS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_OTS
/* GMCS includes a single secondary OTS instance. Mirrors the CAS->CSIS included-
* secondary pattern: create the secondary before the primary so the include
* declaration in the GMCS table resolves to OTS's live handle. */
static struct inc_svc_inst inc_ots_inst;
struct inc_svc_inst *gmcs_not_included_inst(void)
{
if (inc_ots_inst.included == false) {
return &inc_ots_inst;
}
return NULL;
}
static int bluedroid_gmcs_ots_init(void)
{
struct bt_ots *ots;
ots = lib_mcs_get_ots();
if (!ots || !ots->service) {
LOG_ERR("[B]GmcsOtsGetFail");
return -ENODEV;
}
inc_ots_inst.svc_p = ots->service;
/* Reset included before svc_init; see mics.c for rationale. */
inc_ots_inst.included = false;
bt_le_bluedroid_set_svc_in_progress(OTS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(ots->service);
}
static int bluedroid_gmcs_ots_start(void)
{
bt_le_bluedroid_set_svc_in_progress(OTS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(inc_ots_inst.svc_p);
}
#endif /* CONFIG_BT_OTS */
int bt_le_bluedroid_gmcs_init(void)
{
struct bt_gatt_service *gmcs_svc;
int err;
gmcs_svc = lib_mcs_svc_get();
gmcs_svc = lib_gmcs_svc_get();
if (!gmcs_svc) {
LOG_ERR("[B]GmcsSvcGetFail");
return -ENODEV;
}
#if CONFIG_BT_OTS
/* Register the included OTS secondary first so the GMCS include resolves. */
err = bluedroid_gmcs_ots_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_OTS */
bt_le_bluedroid_set_svc_in_progress(GMCS_IN_PROGRESS);
return bt_le_bluedroid_svc_init(gmcs_svc);
err = bt_le_bluedroid_svc_init(gmcs_svc);
if (err) {
return err;
}
#if CONFIG_BT_OTS
if (gmcs_not_included_inst()) {
LOG_ERR("[B]GmcsOtsNotInc");
return -EIO;
}
#endif /* CONFIG_BT_OTS */
return 0;
}
int bt_le_bluedroid_gmcs_start(void)
{
struct bt_gatt_service *gmcs_svc;
int err;
gmcs_svc = lib_mcs_svc_get();
gmcs_svc = lib_gmcs_svc_get();
if (!gmcs_svc) {
LOG_ERR("[B]GmcsSvcGetFail");
return -ENODEV;
}
#if CONFIG_BT_OTS
/* Start the included OTS secondary before GMCS (matches init order). */
err = bluedroid_gmcs_ots_start();
if (err) {
return err;
}
#endif /* CONFIG_BT_OTS */
bt_le_bluedroid_set_svc_in_progress(GMCS_IN_PROGRESS);
return bt_le_bluedroid_svc_start(gmcs_svc);
err = bt_le_bluedroid_svc_start(gmcs_svc);
return err;
}
int bt_le_bluedroid_mcs_init(void)
{
struct bt_gatt_service *list;
int err;
/* NULL when CONFIG_BT_MCS_INSTANCE_COUNT == 0: nothing discrete to add. */
list = lib_mcs_server_list_get();
if (!list) {
LOG_DBG("[B]McsDiscreteOff");
return 0;
}
bt_le_bluedroid_set_svc_in_progress(MCS_IN_PROGRESS);
/* Signed index: count can be 0 (avoid unsigned `< 0` -Werror=type-limits). */
for (int i = 0; i < CONFIG_BT_MCS_INSTANCE_COUNT; i++) {
#if CONFIG_BT_OTS
/* Each discrete MCS instance also includes the single shared OTS secondary
* (created during gmcs_init). Reset the flag so it re-resolves per instance. */
inc_ots_inst.included = false;
#endif /* CONFIG_BT_OTS */
err = bt_le_bluedroid_svc_init(&list[i]);
if (err) {
return err;
}
}
return 0;
}
int bt_le_bluedroid_mcs_start(void)
{
struct bt_gatt_service *list;
int err;
list = lib_mcs_server_list_get();
if (!list) {
return 0;
}
bt_le_bluedroid_set_svc_in_progress(MCS_IN_PROGRESS);
for (int i = 0; i < CONFIG_BT_MCS_INSTANCE_COUNT; i++) {
err = bt_le_bluedroid_svc_start(&list[i]);
if (err) {
return err;
}
}
return 0;
}
@@ -50,3 +50,58 @@ int bt_le_bluedroid_gtbs_start(void)
return bt_le_bluedroid_svc_start(gtbs_svc);
}
int bt_le_bluedroid_tbs_init(void)
{
struct bt_gatt_service *tbs_list;
int err;
/* NULL when BEARER_COUNT==0 (GTBS-only build): nothing discrete to init. */
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
return 0;
}
bt_le_bluedroid_set_svc_in_progress(TBS_IN_PROGRESS);
/* Signed index: BEARER_COUNT can be 0 (GTBS-only), where an unsigned loop var
* would trip -Werror=type-limits. */
for (int i = 0; i < CONFIG_BT_TBS_BEARER_COUNT; i++) {
/* Called once per bearer registration; svc_init assigns attrs[0].handle.
Skip empty (unregistered) and already-added slots so a later
registration doesn't re-create earlier bearers' services. */
if (tbs_list[i].attr_count == 0 || tbs_list[i].attrs[0].handle != 0) {
continue;
}
err = bt_le_bluedroid_svc_init(&tbs_list[i]);
if (err) {
return err;
}
}
return 0;
}
int bt_le_bluedroid_tbs_start(void)
{
struct bt_gatt_service *tbs_list;
int err;
/* NULL when BEARER_COUNT==0 (GTBS-only build): nothing discrete to start. */
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
return 0;
}
bt_le_bluedroid_set_svc_in_progress(TBS_IN_PROGRESS);
for (int i = 0; i < CONFIG_BT_TBS_BEARER_COUNT; i++) {
err = bt_le_bluedroid_svc_start(&tbs_list[i]);
if (err) {
return err;
}
}
return 0;
}
@@ -46,6 +46,7 @@ static bool is_primary_svc(void)
svc_in_progress == GTBS_IN_PROGRESS ||
svc_in_progress == TBS_IN_PROGRESS ||
svc_in_progress == TMAS_IN_PROGRESS ||
svc_in_progress == GMAS_IN_PROGRESS ||
svc_in_progress == VCS_IN_PROGRESS) {
return true;
}
@@ -56,7 +57,8 @@ static bool is_primary_svc(void)
static bool is_secondary_svc(void)
{
if (svc_in_progress == AICS_IN_PROGRESS ||
svc_in_progress == VOCS_IN_PROGRESS) {
svc_in_progress == VOCS_IN_PROGRESS ||
svc_in_progress == OTS_IN_PROGRESS) {
return true;
}
@@ -67,7 +69,9 @@ static bool any_included_svc(void)
{
if (svc_in_progress == CAS_IN_PROGRESS ||
svc_in_progress == MICS_IN_PROGRESS ||
svc_in_progress == VCS_IN_PROGRESS) {
svc_in_progress == VCS_IN_PROGRESS ||
svc_in_progress == GMCS_IN_PROGRESS ||
svc_in_progress == MCS_IN_PROGRESS) {
return true;
}
@@ -116,12 +120,18 @@ static bool is_svc_uuid_valid(uint16_t uuid)
case TMAS_IN_PROGRESS:
return (uuid == BT_UUID_TMAS_VAL);
case GMAS_IN_PROGRESS:
return (uuid == BT_UUID_GMAS_VAL);
case VCS_IN_PROGRESS:
return (uuid == BT_UUID_VCS_VAL);
case VOCS_IN_PROGRESS:
return (uuid == BT_UUID_VOCS_VAL);
case OTS_IN_PROGRESS:
return (uuid == BT_UUID_OTS_VAL);
default:
return false;
}
@@ -264,7 +274,8 @@ static bool is_svc_attr_uuid_valid(uint16_t attr_uuid)
return (attr_uuid == BT_UUID_CSIS_SIRK_VAL ||
attr_uuid == BT_UUID_CSIS_SET_SIZE_VAL ||
attr_uuid == BT_UUID_CSIS_SET_LOCK_VAL ||
attr_uuid == BT_UUID_CSIS_RANK_VAL);
attr_uuid == BT_UUID_CSIS_RANK_VAL ||
attr_uuid == BT_UUID_CSIS_SET_NAME_VAL);
case HAS_IN_PROGRESS:
return (attr_uuid == BT_UUID_HAS_HEARING_AID_FEATURES_VAL ||
@@ -329,6 +340,13 @@ static bool is_svc_attr_uuid_valid(uint16_t attr_uuid)
case TMAS_IN_PROGRESS:
return (attr_uuid == BT_UUID_GATT_TMAPR_VAL);
case GMAS_IN_PROGRESS:
return (attr_uuid == BT_UUID_GMAP_ROLE_VAL ||
attr_uuid == BT_UUID_GMAP_UGG_FEAT_VAL ||
attr_uuid == BT_UUID_GMAP_UGT_FEAT_VAL ||
attr_uuid == BT_UUID_GMAP_BGS_FEAT_VAL ||
attr_uuid == BT_UUID_GMAP_BGR_FEAT_VAL);
case VCS_IN_PROGRESS:
return (attr_uuid == BT_UUID_VCS_STATE_VAL ||
attr_uuid == BT_UUID_VCS_CONTROL_VAL ||
@@ -340,6 +358,16 @@ static bool is_svc_attr_uuid_valid(uint16_t attr_uuid)
attr_uuid == BT_UUID_VOCS_CONTROL_VAL ||
attr_uuid == BT_UUID_VOCS_DESCRIPTION_VAL);
case OTS_IN_PROGRESS:
return (attr_uuid == BT_UUID_OTS_FEATURE_VAL ||
attr_uuid == BT_UUID_OTS_NAME_VAL ||
attr_uuid == BT_UUID_OTS_TYPE_VAL ||
attr_uuid == BT_UUID_OTS_SIZE_VAL ||
attr_uuid == BT_UUID_OTS_ID_VAL ||
attr_uuid == BT_UUID_OTS_PROPERTIES_VAL ||
attr_uuid == BT_UUID_OTS_ACTION_CP_VAL ||
attr_uuid == BT_UUID_OTS_LIST_CP_VAL);
default:
return false;
}
@@ -366,6 +394,14 @@ static struct inc_svc_inst *get_not_included_inst(void)
return vcs_not_included_inst();
#endif /* CONFIG_BT_VCP_VOL_REND */
#if CONFIG_BT_OTS
case GMCS_IN_PROGRESS:
case MCS_IN_PROGRESS:
/* GMCS and every discrete MCS instance include the single shared OTS. */
extern struct inc_svc_inst *gmcs_not_included_inst(void);
return gmcs_not_included_inst();
#endif /* CONFIG_BT_OTS */
default:
return NULL;
}
@@ -488,8 +524,11 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
inc_inst = get_not_included_inst();
if (inc_inst == NULL) {
LOG_ERR("[B]IncSvcNotFound[%u]", svc_in_progress);
return -1;
/* No CSIS registered (csis_svc_p == NULL): skip the include so CAS
* registers standalone, matching the nimble adapter. VCS/MICS size
* their include attrs to the instance count, so never reach here. */
LOG_INF("[B]IncSvcSkip[%u]", svc_in_progress);
break;
}
bt_le_bluedroid_gatts_sem_reset();
@@ -20,6 +20,7 @@
#include "nimble/profiles/tmas.h"
#include "nimble/profiles/vcs.h"
#include "nimble/profiles/has.h"
#include "nimble/profiles/gmas.h"
#ifdef __cplusplus
extern "C" {
@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_NIMBLE_PROFILE_GMAS_H_
#define HOST_NIMBLE_PROFILE_GMAS_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int bt_le_nimble_gmas_attr_handle_set(void);
int bt_le_nimble_gmas_init(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_NIMBLE_PROFILE_GMAS_H_ */
@@ -21,6 +21,10 @@ int bt_le_nimble_gmcs_attr_handle_set(void);
int bt_le_nimble_gmcs_init(bool ots_included);
int bt_le_nimble_mcs_attr_handle_set(void);
int bt_le_nimble_mcs_init(void);
#ifdef __cplusplus
}
#endif
@@ -18,6 +18,10 @@ int bt_le_nimble_gtbs_attr_handle_set(void);
int bt_le_nimble_gtbs_init(void);
int bt_le_nimble_tbs_attr_handle_set(void);
int bt_le_nimble_tbs_init(void);
#ifdef __cplusplus
}
#endif
@@ -121,11 +121,23 @@ LOG_MODULE_REGISTER(LEA_NINIT, CONFIG_BT_ISO_LOG_LEVEL);
#define OTS_CCCD_COUNT 0
#endif /* CONFIG_BT_OTS */
#if CONFIG_BT_TBS
/* GTBS + one discrete bearer (NimBLE maps a single discrete instance). */
#define TBS_CCCD_COUNT (12 + (CONFIG_BT_TBS_BEARER_COUNT > 0 ? 12 : 0))
#else /* CONFIG_BT_TBS */
#define TBS_CCCD_COUNT 0
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_MCS
/* GMCS + CONFIG_BT_MCS_INSTANCE_COUNT discrete instances, all sharing 11 chars. */
#define MCS_CCCD_COUNT (11 * (1 + CONFIG_BT_MCS_INSTANCE_COUNT))
#else /* CONFIG_BT_MCS */
#define MCS_CCCD_COUNT 0
#endif /* CONFIG_BT_MCS */
/* 6 is reserved for other GATT services.
* TODO:
* - OTS
* - MCS (GMCS is used for now)
* - TBS (GTBS is used for now)
*/
#define TOTAL_CCCDS_COUNT (6 + \
(IS_ENABLED(CONFIG_BT_ASCS) ? (1 + ASCS_ASE_SNK_CCCD_COUNT + \
@@ -134,12 +146,12 @@ LOG_MODULE_REGISTER(LEA_NINIT, CONFIG_BT_ISO_LOG_LEVEL);
PAC_SRC_CCCD_COUNT + PAC_SRC_LOC_CCCD_COUNT + \
1 + PACS_SUPPORTED_CONTEXT_CCCD_COUNT) : 0) + \
(IS_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR) ? SCAN_DELEGATOR_RECV_STATE_CCCD_COUNT : 0) + \
(IS_ENABLED(CONFIG_BT_MCS) ? 11 : 0) + \
MCS_CCCD_COUNT + \
(IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER) ? (CSIP_SET_MEMBER_CCCD_COUNT + 2) : 0) + \
(IS_ENABLED(CONFIG_BT_VCP_VOL_REND) ? (1 + VCP_VOL_REND_VOL_FLAGS_CCCD_COUNT) : 0) + \
VOCS_CCCD_COUNT + \
AICS_CCCD_COUNT + \
(IS_ENABLED(CONFIG_BT_TBS) ? 12 : 0) + \
TBS_CCCD_COUNT + \
(IS_ENABLED(CONFIG_BT_HAS) ? (HAS_FEATURES_CCCD_COUNT + \
HAS_PRESET_CONTROL_POINT_CCCD_COUNT + \
HAS_ACTIVE_PRESET_INDEX_CCCD_COUNT) : 0) + \
@@ -195,6 +207,11 @@ int bt_le_nimble_audio_init(void)
if (err) {
return err;
}
err = bt_le_nimble_tbs_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
@@ -273,6 +290,11 @@ static int nimble_gatt_attr_handle_set(void)
if (err) {
return err;
}
err = bt_le_nimble_mcs_attr_handle_set();
if (err) {
return err;
}
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_TBS
@@ -280,6 +302,11 @@ static int nimble_gatt_attr_handle_set(void)
if (err) {
return err;
}
err = bt_le_nimble_tbs_attr_handle_set();
if (err) {
return err;
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
@@ -289,6 +316,13 @@ static int nimble_gatt_attr_handle_set(void)
}
#endif /* CONFIG_BT_HAS */
#if CONFIG_BT_GMAP
err = bt_le_nimble_gmas_attr_handle_set();
if (err) {
return err;
}
#endif /* CONFIG_BT_GMAP */
return err;
}
@@ -343,15 +377,24 @@ static int nimble_gatt_csis_init(struct bt_le_audio_start_info *info,
#if CONFIG_BT_MCS
int bt_le_nimble_media_proxy_pl_init(void)
{
int err;
/* Note:
* Currently the existence of some characteristics within GMCS
* are determined by the enabling of OTS.
*/
#if CONFIG_BT_OTS
return bt_le_nimble_gmcs_init(true);
err = bt_le_nimble_gmcs_init(true);
#else /* CONFIG_BT_OTS */
return bt_le_nimble_gmcs_init(false);
err = bt_le_nimble_gmcs_init(false);
#endif /* CONFIG_BT_OTS */
if (err) {
return err;
}
/* No GMCS rollback on mcs_init failure: it's committed to the NimBLE DB and
referenced until ble_gatts_start; freeing it here would dangle that entry. */
return bt_le_nimble_mcs_init();
}
#endif /* CONFIG_BT_MCS */
@@ -447,6 +490,15 @@ int bt_le_nimble_audio_start(void *info)
#endif /* CONFIG_BT_MICP_MIC_DEV */
#endif /* (BLE_AUDIO_SVC_DEFERRED_ADD == 0) */
#if CONFIG_BT_GMAP
/* Register GMAS before ble_gatts_start: its role-dependent table is filled
* only at esp_ble_audio_gmap_register, which runs before this start phase. */
err = bt_le_nimble_gmas_init();
if (err) {
return err;
}
#endif /* CONFIG_BT_GMAP */
err = ble_gatts_start();
if (err) {
LOG_ERR("[N]GattsStartFail[%d]", err);
@@ -35,7 +35,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#define CSIS_SVC_COUNT CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT
#define CSIS_CHR_COUNT (4 + 1)
#define CSIS_CHR_COUNT (5 + 1)
#if CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE
#define CSIS_CHR_FLAGS_SIRK \
@@ -60,11 +60,20 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#define CSIS_CHR_FLAGS_RANK \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
#if CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE
#define CSIS_CHR_FLAGS_SET_NAME \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
#else /* CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE */
#define CSIS_CHR_FLAGS_SET_NAME \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
#endif /* CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE */
static const ble_uuid16_t csis_uuid_svc = BLE_UUID16_INIT(BT_UUID_CSIS_VAL);
static const ble_uuid16_t csis_uuid_sirk = BLE_UUID16_INIT(BT_UUID_CSIS_SIRK_VAL);
static const ble_uuid16_t csis_uuid_set_size = BLE_UUID16_INIT(BT_UUID_CSIS_SET_SIZE_VAL);
static const ble_uuid16_t csis_uuid_set_lock = BLE_UUID16_INIT(BT_UUID_CSIS_SET_LOCK_VAL);
static const ble_uuid16_t csis_uuid_rank = BLE_UUID16_INIT(BT_UUID_CSIS_RANK_VAL);
static const ble_uuid16_t csis_uuid_set_name = BLE_UUID16_INIT(BT_UUID_CSIS_SET_NAME_VAL);
static struct csis_inst {
struct bt_gatt_service *svc_p;
@@ -72,6 +81,7 @@ static struct csis_inst {
uint16_t set_size_handle;
uint16_t set_lock_handle;
uint16_t rank_handle;
uint16_t set_name_handle;
} csis_insts[CSIS_SVC_COUNT];
static uint8_t csis_svc_count;
@@ -187,6 +197,9 @@ int bt_le_nimble_csis_attr_handle_set(void)
case BT_UUID_CSIS_RANK_VAL:
chr_handle = csis_insts[i].rank_handle;
break;
case BT_UUID_CSIS_SET_NAME_VAL:
chr_handle = csis_insts[i].set_name_handle;
break;
default:
continue;
}
@@ -262,17 +275,21 @@ static void csis_svc_init(struct csis_inst *inst,
csis_chr_init((void *)&svc->characteristics[chr_cnt++], &csis_uuid_rank,
&inst->rank_handle, CSIS_CHR_FLAGS_RANK);
break;
case BT_UUID_CSIS_SET_NAME_VAL:
csis_chr_init((void *)&svc->characteristics[chr_cnt++], &csis_uuid_set_name,
&inst->set_name_handle, CSIS_CHR_FLAGS_SET_NAME);
break;
default:
break;
}
}
/* svc->characteristics has CSIS_CHR_COUNT (= 4 + 1) slots: up to 4 real chars plus
/* svc->characteristics has CSIS_CHR_COUNT (= 5 + 1) slots: up to 5 real chars plus
* a trailing zeroed slot that NimBLE requires as the NULL-uuid array terminator.
* chr_cnt is the real-char count written via [chr_cnt++], so it must stay strictly
* below CSIS_CHR_COUNT — chr_cnt == CSIS_CHR_COUNT means a write already clobbered
* the terminator slot. Trips if the switch matches a 5th char: a new CSIS case added
* without bumping the (4 + 1), or a duplicate UUID in the Zephyr service table.
* the terminator slot. Trips if the switch matches a 6th char: a new CSIS case added
* without bumping the (5 + 1), or a duplicate UUID in the Zephyr service table.
*/
assert(chr_cnt < CSIS_CHR_COUNT);
}
@@ -0,0 +1,203 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <../host/conn_internal.h>
#include "os/os_mbuf.h"
#include "os/os_mempool.h"
#include "host/ble_att.h"
#include "host/ble_gatt.h"
#include "host/ble_hs_mbuf.h"
#include "nimble/server.h"
#include "common/host.h"
#include "../../../lib/include/audio.h"
LOG_MODULE_REGISTER(LEA_GMAS, CONFIG_BT_ISO_LOG_LEVEL);
/* Gaming Audio Service: GMAP Role + one feature char per registered role. The char
* list is built at init from the lib's runtime table (gmas_build_svc) so the NimBLE DB
* matches it 1:1 — handles map by index, no phantom chars. All chars encrypted reads. */
#define GMAS_MAX_CHRS (1 + 4) /* GMAP Role + up to one feature char per role */
static const ble_uuid16_t gmas_svc_uuid = BLE_UUID16_INIT(BT_UUID_GMAS_VAL);
static ble_uuid16_t gmas_chr_uuids[GMAS_MAX_CHRS];
static struct ble_gatt_chr_def gmas_chrs[GMAS_MAX_CHRS + 1];
static struct ble_gatt_svc_def gatt_svc_gmas[2];
/* Mirror the lib's GMAS table (primary + role char + registered feature chars,
each value attr following a 0x2803 declaration) into the NimBLE service def. */
static int gmas_build_svc(struct bt_gatt_service *gmas_svc)
{
bool prev_decl = false;
size_t count = 0;
for (size_t i = 0; i < gmas_svc->attr_count; i++) {
const struct bt_uuid_16 *u = (const struct bt_uuid_16 *)gmas_svc->attrs[i].uuid;
if (u->uuid.type != BT_UUID_TYPE_16) {
prev_decl = false;
continue;
}
if (u->val == BT_UUID_GATT_CHRC_VAL) {
prev_decl = true; /* characteristic declaration; value is next */
continue;
}
if (!prev_decl) {
continue; /* primary service declaration */
}
prev_decl = false;
if (count >= GMAS_MAX_CHRS) {
LOG_ERR("[N]GmasTooManyChr[%u]", (unsigned)gmas_svc->attr_count);
return -ENOMEM;
}
gmas_chr_uuids[count] = (ble_uuid16_t)BLE_UUID16_INIT(u->val);
gmas_chrs[count] = (struct ble_gatt_chr_def) {
.uuid = &gmas_chr_uuids[count].u,
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
.min_key_size = 16,
};
count++;
}
gmas_chrs[count] = (struct ble_gatt_chr_def){0};
gatt_svc_gmas[0] = (struct ble_gatt_svc_def) {
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &gmas_svc_uuid.u,
.includes = NULL,
.characteristics = gmas_chrs,
};
gatt_svc_gmas[1] = (struct ble_gatt_svc_def){0};
return 0;
}
int bt_le_nimble_gmas_attr_handle_set(void)
{
struct bt_gatt_service *gmas_svc;
uint16_t handle = 0;
int rc;
/* App may not register this svc; skip rather than fail audio_start. */
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_GMAS_VAL), &handle);
if (rc) {
LOG_DBG("[N]GmasNotInit");
return 0;
}
gmas_svc = lib_gmas_svc_get();
if (!gmas_svc) {
LOG_ERR("[N]GmasSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]GmasAttrHdlSet[%u][%u]", handle, gmas_svc->attr_count);
for (size_t i = 0; i < gmas_svc->attr_count; i++) {
(gmas_svc->attrs + i)->handle = handle + i;
}
return 0;
}
static int gmas_svc_check(void)
{
struct bt_gatt_service *gmas_svc;
const struct bt_uuid_16 *uuid;
bool chr_found;
/* Post-add integrity check: every char in the built def must exist in the
* lib table (holds by construction now the def is built from it). */
gmas_svc = lib_gmas_svc_get();
if (!gmas_svc) {
LOG_ERR("[N]GmasSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]GmasSvcCheck");
for (const struct ble_gatt_chr_def *chr = gatt_svc_gmas[0].characteristics;
chr && chr->uuid; chr++) {
const ble_uuid16_t *check = (const ble_uuid16_t *)chr->uuid;
chr_found = false;
for (size_t i = 0; i < gmas_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(gmas_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
}
}
if (chr_found == false) {
LOG_ERR("[N]GmasChrNotFound[%04x]", check->value);
return -1;
}
}
return 0;
}
int bt_le_nimble_gmas_init(void)
{
struct bt_gatt_service *gmas_svc;
int rc;
LOG_DBG("[N]GmasInit");
/* GMAP compiled but no role registered (attr_count 0): skip adding GMAS
rather than failing here and blocking all audio startup. */
gmas_svc = lib_gmas_svc_get();
if (!gmas_svc || gmas_svc->attr_count == 0) {
LOG_DBG("[N]GmasNotReg");
return 0;
}
rc = gmas_build_svc(gmas_svc);
if (rc) {
return rc;
}
rc = ble_gatts_count_cfg(gatt_svc_gmas);
if (rc) {
LOG_ERR("[N]GmasCountCfgFail[%d]", rc);
return rc;
}
rc = ble_gatts_add_svcs(gatt_svc_gmas);
if (rc) {
LOG_ERR("[N]GmasAddSvcsFail[%d]", rc);
return rc;
}
rc = gmas_svc_check();
if (rc) {
return rc;
}
return 0;
}
@@ -352,6 +352,12 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
},
};
/* Discrete MCS (0x1848): INSTANCE_COUNT instances sharing GMCS's characteristic table
* and OTS include (val_handle vars are write-only here, so sharing is safe). Built in
* bt_le_nimble_mcs_init as an N+1 NULL-terminated array for the GATT DB lifetime. */
static struct ble_gatt_svc_def *gatt_svc_mcs;
static const ble_uuid16_t mcs_uuid_svc = BLE_UUID16_INIT(BT_UUID_MCS_VAL);
#if CONFIG_BT_OTS
static int inc_ots_attr_handle_set(void)
{
@@ -402,7 +408,7 @@ int bt_le_nimble_gmcs_attr_handle_set(void)
return 0;
}
gmcs_svc = lib_mcs_svc_get();
gmcs_svc = lib_gmcs_svc_get();
if (!gmcs_svc) {
LOG_ERR("[N]GmcsSvcGetFail");
return -ENODEV;
@@ -448,7 +454,7 @@ static int gmcs_svc_check(void)
* the service exist in the service defined by Zephyr.
*/
gmcs_svc = lib_mcs_svc_get();
gmcs_svc = lib_gmcs_svc_get();
if (!gmcs_svc) {
LOG_ERR("[N]GmcsSvcGetFail");
return -ENODEV;
@@ -713,3 +719,133 @@ free:
#endif /* CONFIG_BT_OTS */
return rc;
}
int bt_le_nimble_mcs_attr_handle_set(void)
{
struct bt_gatt_service *mcs_list;
uint16_t start_handle = 0;
int rc;
/* ble_gatts_find_svc returns the first 0x1848 (instance 0). All N instances
* were registered contiguously in one ble_gatts_add_svcs call and share the
* same attr_count, so instance i starts at start_handle + i * attr_count. */
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MCS_VAL), &start_handle);
if (rc) {
LOG_DBG("[N]McsNotInit");
return 0;
}
mcs_list = lib_mcs_server_list_get();
if (!mcs_list) {
LOG_ERR("[N]McsSvcGetFail");
return -ENODEV;
}
for (int inst = 0; inst < CONFIG_BT_MCS_INSTANCE_COUNT; inst++) {
struct bt_gatt_service *svc = &mcs_list[inst];
uint16_t base = start_handle + inst * svc->attr_count;
LOG_DBG("[N]McsAttrHdlSet[%d][%u][%u]", inst, base, svc->attr_count);
for (size_t i = 0; i < svc->attr_count; i++) {
(svc->attrs + i)->handle = base + i;
}
}
return 0;
}
static int mcs_svc_check(void)
{
struct bt_gatt_service *mcs_svc;
const struct bt_uuid_16 *uuid;
bool chr_found;
mcs_svc = lib_mcs_server_list_get();
if (!mcs_svc) {
LOG_ERR("[N]McsSvcGetFail");
return -ENODEV;
}
LOG_DBG("[N]McsSvcCheck");
for (const struct ble_gatt_chr_def *chr = gatt_svc_mcs[0].characteristics;
chr && chr->uuid; chr++) {
const ble_uuid16_t *check = (const ble_uuid16_t *)chr->uuid;
chr_found = false;
for (size_t i = 0; i < mcs_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(mcs_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
}
}
if (chr_found == false) {
LOG_ERR("[N]McsChrNotFound[%04x]", check->value);
return -1;
}
}
return 0;
}
int bt_le_nimble_mcs_init(void)
{
uint8_t count = CONFIG_BT_MCS_INSTANCE_COUNT;
int rc;
/* NULL when CONFIG_BT_MCS_INSTANCE_COUNT == 0: nothing discrete to add. */
if (!lib_mcs_server_list_get()) {
LOG_DBG("[N]McsDiscreteOff");
return 0;
}
LOG_DBG("[N]McsInit[%d]", count);
/* One ble_gatt_svc_def per instance + a zeroed terminator. Persists for the
* GATT DB lifetime (NimBLE references these defs until ble_gatts_start). */
gatt_svc_mcs = calloc(count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_mcs);
for (int i = 0; i < count; i++) {
gatt_svc_mcs[i].type = BLE_GATT_SVC_TYPE_PRIMARY;
gatt_svc_mcs[i].uuid = &mcs_uuid_svc.u;
/* All instances share the GMCS characteristic table (val_handle vars are
* write-only here, so sharing is safe). */
gatt_svc_mcs[i].characteristics = gatt_svc_gmcs[0].characteristics;
#if CONFIG_BT_OTS
/* Each instance includes the same shared OTS secondary as GMCS. gmcs_init
* runs first and builds gmcs_inc_svcs (NULL when OTS is not included). */
gatt_svc_mcs[i].includes = (const struct ble_gatt_svc_def **)gmcs_inc_svcs;
#endif /* CONFIG_BT_OTS */
}
rc = ble_gatts_count_cfg(gatt_svc_mcs);
if (rc) {
LOG_ERR("[N]McsCountCfgFail[%d]", rc);
goto free;
}
rc = ble_gatts_add_svcs(gatt_svc_mcs);
if (rc) {
LOG_ERR("[N]McsAddSvcsFail[%d]", rc);
goto free;
}
rc = mcs_svc_check();
if (rc) {
goto free;
}
return 0;
free:
free(gatt_svc_mcs);
gatt_svc_mcs = NULL;
return rc;
}
@@ -162,6 +162,20 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
},
};
/* Discrete TBS (0x184B) shares GTBS's characteristic table, bound at init (a
* static initializer can't reference another object's member). */
static struct ble_gatt_svc_def gatt_svc_tbs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_VAL),
.includes = NULL,
.characteristics = NULL,
},
{
0, /* No more services. */
},
};
int bt_le_nimble_gtbs_attr_handle_set(void)
{
struct bt_gatt_service *gtbs_svc;
@@ -260,3 +274,101 @@ int bt_le_nimble_gtbs_init(void)
return 0;
}
int bt_le_nimble_tbs_attr_handle_set(void)
{
struct bt_gatt_service *tbs_list;
uint16_t handle = 0;
int rc;
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_TBS_VAL), &handle);
if (rc) {
LOG_DBG("[N]TbsNotInit");
return 0;
}
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
LOG_ERR("[N]TbsSvcListGetFail");
return -ENODEV;
}
/* ble_gatts_find_svc returns the first match, so only the single discrete
* bearer is mapped (sufficient for CONFIG_BT_TBS_BEARER_COUNT==1).
*/
LOG_DBG("[N]TbsAttrHdlSet[%u][%u]", handle, tbs_list[0].attr_count);
for (size_t i = 0; i < tbs_list[0].attr_count; i++) {
(tbs_list[0].attrs + i)->handle = handle + i;
}
return 0;
}
static int tbs_svc_check(void)
{
struct bt_gatt_service *tbs_list;
const struct bt_uuid_16 *uuid;
bool chr_found;
tbs_list = lib_tbs_server_list_get();
if (!tbs_list) {
LOG_ERR("[N]TbsSvcListGetFail");
return -ENODEV;
}
LOG_DBG("[N]TbsSvcCheck");
for (const struct ble_gatt_chr_def *chr = gatt_svc_tbs[0].characteristics;
chr && chr->uuid; chr++) {
const ble_uuid16_t *check = (const ble_uuid16_t *)chr->uuid;
chr_found = false;
for (size_t i = 0; i < tbs_list[0].attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(tbs_list[0].attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
}
}
if (chr_found == false) {
LOG_ERR("[N]TbsChrNotFound[%04x]", check->value);
return -1;
}
}
return 0;
}
int bt_le_nimble_tbs_init(void)
{
int rc;
LOG_DBG("[N]TbsInit");
/* Bind the shared characteristic table (see gatt_svc_tbs comment). */
gatt_svc_tbs[0].characteristics = gatt_svc_gtbs[0].characteristics;
rc = ble_gatts_count_cfg(gatt_svc_tbs);
if (rc) {
LOG_ERR("[N]TbsCountCfgFail[%d]", rc);
return rc;
}
rc = ble_gatts_add_svcs(gatt_svc_tbs);
if (rc) {
LOG_ERR("[N]TbsAddSvcsFail[%d]", rc);
return rc;
}
rc = tbs_svc_check();
if (rc) {
return rc;
}
return 0;
}
@@ -53,6 +53,8 @@ int bt_le_tmas_init(void);
int bt_le_gtbs_init(void);
int bt_le_tbs_init(void);
int bt_le_has_init(void);
int bt_le_media_proxy_pl_init(void);
@@ -63,10 +65,10 @@ int bt_le_micp_mic_dev_init(void);
int bt_le_audio_start(void *info);
void ble_audio_lib_compressed_out(uint8_t log_level, uint32_t log_index, size_t arg_cnt, ...);
void bt_le_audio_lib_compressed_out(uint8_t log_level, uint32_t log_index, size_t arg_cnt, ...);
void ble_audio_lib_compressed_buf_out(uint8_t log_level, uint32_t log_index, uint8_t buf_idx,
const uint8_t *buf, size_t len);
void bt_le_audio_lib_compressed_buf_out(uint8_t log_level, uint32_t log_index, uint8_t buf_idx,
const uint8_t *buf, size_t len);
#ifdef __cplusplus
}
+46 -26
View File
@@ -69,10 +69,6 @@ _Static_assert(offsetof(struct bt_le_audio_start_info, csis_insts) ==
"Mismatch LE Audio start info structure");
#endif /* CONFIG_BT_CSIP_SET_MEMBER */
#if CONFIG_BT_TBS
_Static_assert(CONFIG_BT_TBS_BEARER_COUNT == 0, "Currently only support GTBS");
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_OTS && !CONFIG_BT_OTS_SECONDARY_SVC
_Static_assert(0, "Currently only support using OTS as Secondary Service");
#endif /* CONFIG_BT_OTS && !CONFIG_BT_OTS_SECONDARY_SVC */
@@ -182,7 +178,7 @@ static const uint16_t ext_structs[] = {
sizeof(struct bt_bond_info),
};
#define LEA_VERSION (0x20260624)
#define LEA_VERSION (0x20260722)
struct lib_ext_cfgs {
/* BLE */
@@ -269,6 +265,7 @@ struct lib_ext_cfgs {
bool config_csip_set_member_enc_sirk_support;
bool config_csip_set_member_sirk_notifiable;
bool config_csip_set_member_size_notifiable;
bool config_csip_set_member_set_name_notifiable;
bool config_csip_set_member_test_sample_data;
bool config_csip_set_coordinator;
uint8_t config_csip_set_coordinator_max_csis_instances;
@@ -294,6 +291,7 @@ struct lib_ext_cfgs {
/* MCS (Media Control Service) */
bool config_mcs;
uint8_t config_mcs_instance_count;
bool config_mcc;
uint8_t config_mcc_media_player_name_max;
uint8_t config_mcc_icon_url_max;
@@ -437,6 +435,9 @@ struct lib_ext_cfgs {
bool config_ots_client;
uint8_t config_ots_obj_max_name_len;
/* PTS */
bool config_pts_test_enable;
/* Version Check */
uint32_t config_version;
};
@@ -592,6 +593,9 @@ static const struct lib_ext_cfgs ext_cfgs = {
#if CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE
.config_csip_set_member_size_notifiable = CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE,
#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */
#if CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE
.config_csip_set_member_set_name_notifiable = CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE,
#endif /* CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE */
#if CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA
.config_csip_set_member_test_sample_data = CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA,
#endif /* CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA */
@@ -649,6 +653,7 @@ static const struct lib_ext_cfgs ext_cfgs = {
/* MCS (Media Control Service) */
#if CONFIG_BT_MCS
.config_mcs = CONFIG_BT_MCS,
.config_mcs_instance_count = CONFIG_BT_MCS_INSTANCE_COUNT,
#endif /* CONFIG_BT_MCS */
#if CONFIG_BT_MCC
.config_mcc = CONFIG_BT_MCC,
@@ -969,6 +974,9 @@ static const struct lib_ext_cfgs ext_cfgs = {
.config_ots_obj_max_name_len = CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
/* PTS test mode: gates PTS-only changes. Set true only for PTS builds. */
.config_pts_test_enable = false,
.config_version = LEA_VERSION,
};
@@ -1345,7 +1353,7 @@ static const struct lib_ext_funcs ext_funcs = {
#endif /* CONFIG_BT_OTS_CLIENT */
};
struct lib_funcs {
struct lib_int_funcs {
/* AICS Client */
int (*_aics_client_state_get)(struct bt_aics *inst);
int (*_aics_client_gain_setting_get)(struct bt_aics *inst);
@@ -1534,7 +1542,7 @@ struct lib_funcs {
int (*_vocs_client_description_set)(struct bt_vocs_client *inst, const char *description);
};
static const struct lib_funcs lib_funcs = {
static const struct lib_int_funcs int_funcs = {
#if CONFIG_BT_AICS_CLIENT
._aics_client_state_get = lib_aics_client_state_get,
._aics_client_gain_setting_get = lib_aics_client_gain_setting_get,
@@ -1742,7 +1750,7 @@ static const struct lib_funcs lib_funcs = {
#endif /* CONFIG_BT_VOCS_CLIENT */
};
static int lib_resources_init(void)
static int lib_audio_resources_init(void)
{
int err = 0;
@@ -2077,27 +2085,27 @@ int bt_le_audio_init(void)
{
int err;
err = lib_ext_structs_check(ext_structs, sizeof(ext_structs));
err = lib_audio_ext_structs_check(ext_structs, sizeof(ext_structs));
if (err) {
LOG_ERR("LibExtStructsCheckFail");
return err;
}
err = lib_ext_cfgs_set(&ext_cfgs, sizeof(ext_cfgs));
err = lib_audio_ext_cfgs_set(&ext_cfgs, sizeof(ext_cfgs));
if (err) {
LOG_ERR("LibExtCfgsSetFail");
return err;
}
err = lib_ext_funcs_set(&ext_funcs, sizeof(ext_funcs));
err = lib_audio_ext_funcs_set(&ext_funcs, sizeof(ext_funcs));
if (err) {
LOG_ERR("LibExtFuncsSetFail");
return err;
}
err = lib_funcs_set(&lib_funcs, sizeof(lib_funcs));
err = lib_audio_int_funcs_set(&int_funcs, sizeof(int_funcs));
if (err) {
LOG_ERR("LibFuncsSetFail");
LOG_ERR("LibIntFuncsSetFail");
return err;
}
@@ -2105,9 +2113,10 @@ int bt_le_audio_init(void)
BT_ISO_LOG_COLOR_I
"I (%lu) %s: BLE Audio lib commit: [%s]"
BT_ISO_LOG_RESET_COLOR "\n",
esp_log_timestamp(), LEA_TAG, lib_ext_commit_get());
esp_log_timestamp(), LEA_TAG,
lib_audio_commit_get());
err = lib_resources_init();
err = lib_audio_resources_init();
if (err) {
return err;
}
@@ -2170,6 +2179,17 @@ int bt_le_gtbs_init(void)
return bt_le_nimble_gtbs_init();
#endif
}
int bt_le_tbs_init(void)
{
LOG_DBG("TbsInit");
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_tbs_init();
#else
return bt_le_nimble_tbs_init();
#endif
}
#endif /* CONFIG_BT_TBS */
#if CONFIG_BT_HAS
@@ -2236,7 +2256,7 @@ int bt_le_audio_start(void *info)
#endif
}
void ble_audio_lib_compressed_out(uint8_t log_level, uint32_t log_index, size_t arg_cnt, ...)
void bt_le_audio_lib_compressed_out(uint8_t log_level, uint32_t log_index, size_t arg_cnt, ...)
{
#if CONFIG_BLE_ISO_COMPRESSED_LOG_ENABLE
if (CONFIG_BT_ISO_LOG_LEVEL >= log_level) {
@@ -2249,14 +2269,14 @@ void ble_audio_lib_compressed_out(uint8_t log_level, uint32_t log_index, size_t
va_end(args);
}
#else
(void)log_level;
(void)log_index;
(void)arg_cnt;
ARG_UNUSED(log_level);
ARG_UNUSED(log_index);
ARG_UNUSED(arg_cnt);
#endif
}
void ble_audio_lib_compressed_buf_out(uint8_t log_level, uint32_t log_index, uint8_t buf_idx,
const uint8_t *buf, size_t len)
void bt_le_audio_lib_compressed_buf_out(uint8_t log_level, uint32_t log_index, uint8_t buf_idx,
const uint8_t *buf, size_t len)
{
#if CONFIG_BLE_ISO_COMPRESSED_LOG_ENABLE
if (CONFIG_BT_ISO_LOG_LEVEL >= log_level) {
@@ -2267,10 +2287,10 @@ void ble_audio_lib_compressed_buf_out(uint8_t log_level, uint32_t log_index, uin
log_index, buf_idx, buf, len);
}
#else
(void)log_level;
(void)log_index;
(void)buf_idx;
(void)buf;
(void)len;
ARG_UNUSED(log_level);
ARG_UNUSED(log_index);
ARG_UNUSED(buf_idx);
ARG_UNUSED(buf);
ARG_UNUSED(len);
#endif
}
@@ -48,6 +48,11 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
/* Prepare buffer for sending. */
buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER);
if (buf == NULL) {
LOG_ERR("OtsTxBufAllocFail");
return -ENOMEM;
}
net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, &l2cap_ctx->tx.data[l2cap_ctx->tx.len_sent], len);
@@ -1594,6 +1594,25 @@ int bt_bap_unicast_server_config_ase_safe(struct bt_conn *conn, struct bt_bap_st
struct bt_audio_codec_cfg *codec_cfg,
const struct bt_bap_qos_cfg_pref *qos_pref);
/**
* @brief Initialize and configure a new ASE of a specific direction.
*
* Like bt_bap_unicast_server_config_ase(), but configures the first free ASE of
* the requested direction so a server can initiate a config on a source ASE.
*
* @param conn Connection object
* @param stream Configured stream object to be attached to the ASE
* @param codec_cfg Codec configuration
* @param qos_pref Audio Stream Quality of Service Preference
* @param dir ASE direction to configure (sink or source)
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_bap_unicast_server_config_ase_with_dir(struct bt_conn *conn, struct bt_bap_stream *stream,
struct bt_audio_codec_cfg *codec_cfg,
const struct bt_bap_qos_cfg_pref *qos_pref,
enum bt_audio_dir dir);
/** @} */ /* End of group bt_bap_unicast_server */
/**
@@ -867,6 +867,11 @@ struct bt_cap_handover_unicast_to_broadcast_param {
/** The source unicast group with the streams. */
struct bt_cap_unicast_group *unicast_group;
/**
* @brief Advertising handle of the broadcast source.
*/
uint8_t adv_handle;
/**
* @brief The advertising set to use for the broadcast source
*
@@ -972,6 +977,8 @@ int bt_cap_handover_unregister_cb_safe(const struct bt_cap_handover_cb *cb);
*
* @return 0 on success or negative error value on failure.
*/
int bt_cap_handover_unicast_to_broadcast(
const struct bt_cap_handover_unicast_to_broadcast_param *param);
int bt_cap_handover_unicast_to_broadcast_safe(
const struct bt_cap_handover_unicast_to_broadcast_param *param);
@@ -56,11 +56,14 @@ extern "C" {
#define BT_CSIP_READ_SIRK_REQ_RSP_OOB_ONLY 0x03
/** Size of the Set Identification Resolving Key (SIRK) */
#define BT_CSIP_SIRK_SIZE 16
#define BT_CSIP_SIRK_SIZE 16
/** Size of the Resolvable Set Identifier (RSI) */
#define BT_CSIP_RSI_SIZE 6
/** Maximum length of the Coordinated Set Name (CSIS v1.1), a 0-128 octet UTF-8 string */
#define BT_CSIP_SET_NAME_MAX_LEN 128
/* Coordinate Set Identification Service Error codes */
/** Service is already locked */
#define BT_CSIP_ERROR_LOCK_DENIED 0x80
@@ -132,6 +135,17 @@ struct bt_csip_set_member_register_param {
*/
uint8_t set_size;
/**
* @brief Coordinated Set Name (CSIS v1.1), a 0-128 octet UTF-8 string.
*
* If @ref set_name_len is 0, the Coordinated Set Name characteristic
* won't be initialized.
*/
uint8_t set_name[BT_CSIP_SET_NAME_MAX_LEN];
/** Length in octets of @ref set_name. */
uint8_t set_name_len;
/**
* @brief The unique Set Identity Resolving Key (SIRK)
*
@@ -245,6 +259,24 @@ int bt_csip_set_member_sirk_safe(struct bt_csip_set_member_svc_inst *svc_inst,
int bt_csip_set_member_set_size_and_rank_safe(struct bt_csip_set_member_svc_inst *svc_inst, uint8_t size,
uint8_t rank);
/**
* @brief Set the Coordinated Set Name (CSIS v1.1) of a service instance.
*
* If @kconfig{CONFIG_BT_CSIP_SET_MEMBER_SET_NAME_NOTIFIABLE} is enabled, this also notifies
* subscribed clients (the first ATT_MTU-3 octets if the name is longer).
*
* @param svc_inst The service instance.
* @param name The new name (UTF-8). May be NULL only if @p len is 0.
* @param len Length of @p name in octets (0 to @ref BT_CSIP_SET_NAME_MAX_LEN).
*
* @retval -EINVAL @p svc_inst is NULL, @p len exceeds BT_CSIP_SET_NAME_MAX_LEN, or @p name is
* NULL with a non-zero @p len.
* @retval -EALREADY The name is already set to this value.
* @retval 0 Success.
*/
int bt_csip_set_member_set_name_safe(struct bt_csip_set_member_svc_inst *svc_inst,
const uint8_t *name, uint8_t len);
/** Struct to hold information about a service instance */
struct bt_csip_set_member_set_info {
/** The 16-octet SIRK */
@@ -311,6 +311,20 @@ int bt_has_client_preset_next_safe(struct bt_has *has, bool sync);
*/
int bt_has_client_preset_prev_safe(struct bt_has *has, bool sync);
/**
* @brief Write Preset Name.
*
* Client procedure to change the name of the preset identified by @p index.
* The result is reflected by the server via a Preset Changed notification.
*
* @param has Pointer to the Hearing Access Service object.
* @param index Preset index whose name to change.
* @param name New preset name (BT_HAS_PRESET_NAME_MIN..BT_HAS_PRESET_NAME_MAX octets).
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_client_preset_name_write_safe(struct bt_has *has, uint8_t index, const char *name);
/** @brief Preset operations structure. */
struct bt_has_preset_ops {
/**
@@ -1603,6 +1603,24 @@ int bt_media_proxy_pl_register_safe(struct media_proxy_pl_calls *pl_calls);
*/
int bt_media_proxy_pl_init_safe(void);
/**
* @brief Set the player name and notify it
*
* @param name The new player name (caller-owned, must outlive later reads)
*
* @return 0 if success, errno on failure
*/
int bt_media_proxy_pl_set_player_name_safe(char *name);
/**
* @brief Set the current track title and notify it
*
* @param title The new track title (caller-owned, must outlive later reads)
*
* @return 0 if success, errno on failure
*/
int bt_media_proxy_pl_set_track_title_safe(char *title);
/**
* @brief Get the pointer of the Object Transfer Service used by the Media Control Service
*
@@ -410,6 +410,51 @@ int bt_tbs_remote_terminate_safe(uint8_t call_index);
int bt_tbs_remote_incoming_safe(uint8_t bearer_index, const char *to,
const char *from, const char *friendly_name);
/**
* @brief Create a call directly in a given state (test setup helper).
*
* Allocates a call on the bearer and sets it to @p state without running the
* call state machine, bypassing the single-outgoing-call restriction and the
* Dialing->Alerting auto-promotion. Intended for setting up the fixed call
* configurations required by the Join test procedures (e.g. a stable Dialing
* call coexisting with an Alerting one). The call is notified once.
*
* @param[in] bearer_index The index of the Telephone Bearer.
* @param[in] state The initial call state (BT_TBS_CALL_STATE_*).
* @param[in] uri The remote URI stored for the call.
* @param[out] call_index Where the new call index is stored.
*
* @return int New call index if positive or 0,
* errno value if negative.
*/
int bt_tbs_add_call_safe(uint8_t bearer_index, uint8_t state, const char *uri,
uint8_t *call_index);
/**
* @brief Enable or disable automatic Dialing->Alerting promotion (test control).
*
* When disabled, an originated call on the bearer remains in the Dialing state
* until bt_tbs_set_call_alerting() is called. This lets a test harness control
* the transition timing and keep multiple outgoing calls in Dialing at once.
* Enabled by default.
*
* @param bearer_index The index of the Telephone Bearer or BT_TBS_GTBS_INDEX.
* @param enable true to auto-promote (default), false to keep Dialing.
*
* @return int 0 on success, errno value if negative.
*/
int bt_tbs_set_auto_alerting_safe(uint8_t bearer_index, bool enable);
/**
* @brief Move a Dialing call to the Alerting state (test setup helper).
*
* @param call_index The call index to promote.
*
* @return int BT_TBS_RESULT_CODE_* if positive or 0,
* errno value if negative.
*/
int bt_tbs_set_call_alerting_safe(uint8_t call_index);
/**
* @brief Set a new bearer provider.
*
@@ -250,6 +250,46 @@ int bt_vcp_vol_rend_unmute_safe(void);
*/
int bt_vcp_vol_rend_mute_safe(void);
/**
* @name Volume Renderer reset field selectors
*
* Bitmask values for @ref bt_vcp_vol_rend_reset_param.fields selecting
* which state fields a reset applies.
* @{
*/
/** Apply the volume field; also clears Volume_Setting_Persisted (persisted = 0). */
#define BT_VCP_VOL_REND_RESET_VOLUME BIT(0)
/** Apply the mute field. */
#define BT_VCP_VOL_REND_RESET_MUTE BIT(1)
/** @} */
/** Parameters for @ref bt_vcp_vol_rend_reset_state_safe. */
struct bt_vcp_vol_rend_reset_param {
/** Bitmask of BT_VCP_VOL_REND_RESET_* selecting which fields to apply. */
uint32_t fields;
/** Volume setting, applied when BT_VCP_VOL_REND_RESET_VOLUME is set. */
uint8_t volume;
/** Mute state, applied when BT_VCP_VOL_REND_RESET_MUTE is set. */
uint8_t mute;
};
/**
* @brief Reset selected volume state fields to an initial/reset value.
*
* For each field selected in @p param->fields, sets it to the provided value
* without treating it as a user change. When the volume field is applied,
* Volume_Setting_Persisted returns to Reset Volume Setting (0) - the value is a
* server reset value, not a modification (VCS v1.0.1 Section 3.3.1). Unselected
* fields are left unchanged; no notification is emitted.
*
* @param param Reset parameters. Must not be NULL.
*
* @return 0 if success, errno on failure.
*/
int bt_vcp_vol_rend_reset_state_safe(const struct bt_vcp_vol_rend_reset_param *param);
/**
* @brief Struct to hold the Volume Controller callbacks
*
@@ -18,13 +18,13 @@ extern "C" {
struct lib_ext_cfgs;
struct lib_ext_funcs;
struct lib_funcs;
struct lib_int_funcs;
extern int lib_ext_structs_check(const uint16_t *ext_structs, size_t size_structs);
extern int lib_ext_cfgs_set(const struct lib_ext_cfgs *ext_cfgs, size_t size_cfgs);
extern int lib_ext_funcs_set(const struct lib_ext_funcs *ext_funcs, size_t size_funcs);
extern int lib_funcs_set(const struct lib_funcs *funcs, size_t size_funcs);
extern const char *lib_ext_commit_get(void);
extern int lib_audio_ext_structs_check(const uint16_t *ext_structs, size_t size_structs);
extern int lib_audio_ext_cfgs_set(const struct lib_ext_cfgs *ext_cfgs, size_t size_cfgs);
extern int lib_audio_ext_funcs_set(const struct lib_ext_funcs *ext_funcs, size_t size_funcs);
extern int lib_audio_int_funcs_set(const struct lib_int_funcs *funcs, size_t size_funcs);
extern const char *lib_audio_commit_get(void);
struct bt_aics;
struct bt_gatt_service;
@@ -79,7 +79,8 @@ extern int lib_has_init(void);
extern int lib_has_client_init(void);
extern int lib_mcc_init(void);
extern struct bt_gatt_service *lib_mcs_svc_get(void);
extern struct bt_gatt_service *lib_gmcs_svc_get(void);
extern struct bt_gatt_service *lib_mcs_server_list_get(void);
extern int lib_mcs_init(void);
extern int lib_media_proxy_init(void);
+2 -2
View File
@@ -80,7 +80,7 @@ if BT_ISO
config BT_ISO_MAX_CIG
int "Maximum number of Connected Isochronous Groups (CIGs) to support"
range 1 1 if BT_BLUEDROID_ENABLED
range 1 2 if BT_BLUEDROID_ENABLED
range 1 BT_NIMBLE_ISO_CIG if BT_NIMBLE_ENABLED
default 1 if BT_BLUEDROID_ENABLED
default BT_NIMBLE_ISO_CIG if BT_NIMBLE_ENABLED
@@ -94,7 +94,7 @@ if BT_ISO
config BT_ISO_MAX_BIG
int "Maximum number of Broadcast Isochronous Groups (BIGs) to support"
range 1 1 if BT_BLUEDROID_ENABLED
range 1 2 if BT_BLUEDROID_ENABLED
range 1 BT_NIMBLE_ISO_BIG if BT_NIMBLE_ENABLED
default 1 if BT_BLUEDROID_ENABLED
default BT_NIMBLE_ISO_BIG if BT_NIMBLE_ENABLED
@@ -41,8 +41,10 @@ esp_err_t esp_ble_iso_data_parse(const uint8_t ltv[], size_t size,
continue;
}
/* A callback returning false aborts parsing; report it as an error so
* callers detect it (bt_audio_data_parse() returns -ECANCELED here). */
if (func(type, &ltv[i + 2], data_len, user_data) == false) {
return ESP_OK;
return ESP_FAIL;
}
i += (size_t)len + 1;
@@ -233,7 +235,8 @@ esp_err_t esp_ble_iso_big_ext_adv_add(esp_ble_iso_ext_adv_info_t *info)
return ESP_ERR_INVALID_ARG;
}
err = bt_le_ext_adv_new_safe(info->adv_handle);
err = bt_le_ext_adv_new_safe(info->adv_handle, info->addr_type,
info->addr, info->sid);
if (err) {
return ESP_FAIL;
}
@@ -287,6 +287,9 @@ typedef struct bt_iso_tx_cb_info esp_ble_iso_tx_cb_info_t;
/** Extended advertising information structure */
typedef struct {
uint8_t adv_handle; /*!< Handle for the advertising set */
uint8_t addr_type; /*!< Advertising address type (0=public, 1=random) */
uint8_t addr[6]; /*!< Advertising address */
uint8_t sid; /*!< Advertising SID */
} esp_ble_iso_ext_adv_info_t;
/**
@@ -13,6 +13,7 @@
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/common/bt_str.h>
#include <../host/hci_core.h>
#include <../host/iso_internal.h>
@@ -104,6 +105,46 @@ static void gap_app_cb(tBTA_DM_BLE_5_GAP_EVENT event, tBTA_DM_BLE_5_GAP_CB_PARAM
btc_ble_5_gap_callback(event, params);
}
/* Copy this peer's bonded LTK (16B) into out_ltk; true if found. Used on AUTH_CMPL as
* the CSIS SIRK key. The store exposes only PENC, but under LE SC PENC==LENC==link LTK.
* esp_ble_get_bond_device_list reads btc_storage synchronously — safe in this callback. */
static bool bd_read_bonded_ltk(const uint8_t *addr, uint8_t out_ltk[16])
{
esp_ble_bond_dev_t *list;
bool found = false;
int num;
num = esp_ble_get_bond_device_num();
if (num <= 0) {
return false;
}
list = calloc(num, sizeof(*list));
if (list == NULL) {
LOG_ERR("[B]LtkListAllocFail[%d]", num);
return false;
}
if (esp_ble_get_bond_device_list(&num, list) == ESP_OK) {
for (int i = 0; i < num; i++) {
if (memcmp(list[i].bd_addr, addr, sizeof(esp_bd_addr_t)) == 0) {
/* Identity-only bonds carry no LTK; penc_key would be zeroed. */
if (list[i].bond_key.key_mask & ESP_BLE_ENC_KEY_MASK) {
memcpy(out_ltk, list[i].bond_key.penc_key.ltk, 16);
found = true;
}
break;
}
}
}
if (list != NULL) {
free(list);
}
return found;
}
void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
{
const esp_ble_gap_cb_param_t *p = param;
@@ -157,7 +198,16 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
qev->security_change.sec_level = sec_level;
qev->security_change.bonded = (a->auth_mode & ESP_LE_AUTH_BOND) ? 1 : 0;
/* Attach the bonded LTK (CSIS SIRK-encryption key K). */
if (bd_read_bonded_ltk(a->bd_addr, qev->security_change.ltk)) {
qev->security_change.ltk_present = 1;
LOG_INF("[B]LtkFromStore[%u]", qev->security_change.conn_handle);
} else {
LOG_WRN("[B]NoLtkForEnc[%u]", qev->security_change.conn_handle);
}
}
break;
}
@@ -237,7 +287,8 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
LOG_WRN("[B]PaSyncEstabOverwrite[%04x->%04x]",
active_pa_sync_handle, e->sync_handle);
} else {
LOG_INF("[B]PaSyncEstab[%04x]", e->sync_handle);
LOG_INF("[B]PaSyncEstab[%04x][%s]",
e->sync_handle, bt_hex(e->adv_addr, BT_ADDR_SIZE));
}
active_pa_sync_handle = e->sync_handle;
}
@@ -236,16 +236,26 @@ static void gatts_notify_list_drain(struct gatt_conn *gatt_conn)
}
}
/* Rewrite a slot's conn_id to the GATTS/GATTC gatt_if (low byte); the BTA index
* (high byte) is shared on one ACL. Lets a dual-role link op on the right conn_id. */
static inline uint16_t to_gatts_conn_id(uint16_t conn_id)
{
return BTC_GATT_CREATE_CONN_ID(gatts_if, BTC_GATT_GET_CONN_ID(conn_id));
}
static inline uint16_t to_gattc_conn_id(uint16_t conn_id)
{
return BTC_GATT_CREATE_CONN_ID(gattc_if, BTC_GATT_GET_CONN_ID(conn_id));
}
static void gatts_indicate_dispatch(struct bt_conn *conn,
uint16_t conn_id,
struct gatts_list_node *n)
{
uint16_t conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn->handle);
LOG_DBG("[B]GattsIndDispatch[%u][%u]", conn->handle, n->value_handle);
BTA_GATTS_HandleValueIndication(conn_id, n->value_handle,
n->params_copy.len,
n->data_copy, true);
BTA_GATTS_HandleValueIndication(to_gatts_conn_id(conn_id), n->value_handle,
n->params_copy.len, n->data_copy, true);
}
/* Silent drain — used by reset_gatt_conn as a safety net. The disconnect
@@ -348,11 +358,58 @@ struct gatt_conn *bt_le_bluedroid_find_free_gatt_conn(void)
return NULL;
}
/* Exact slot lookup by BTA conn_id (full gatt_if + connection index). */
static struct gatt_conn *find_gatt_conn_with_conn_id(uint16_t conn_id)
{
for (size_t i = 0; i < ARRAY_SIZE(gatt_conns); i++) {
if (gatt_conns[i].used && gatt_conns[i].conn_id == conn_id) {
return &gatt_conns[i];
}
}
return NULL;
}
/* Slot lookup for a BTA request/completion: exact conn_id, else fall back to the
* connection index (high byte) so a dual-role link resolves either gatt_if. */
static struct gatt_conn *find_gatt_conn_by_conn_id_or_index(uint16_t conn_id)
{
struct gatt_conn *gatt_conn = find_gatt_conn_with_conn_id(conn_id);
if (gatt_conn == NULL) {
for (size_t i = 0; i < ARRAY_SIZE(gatt_conns); i++) {
if (gatt_conns[i].used &&
BTC_GATT_GET_CONN_ID(gatt_conns[i].conn_id) == BTC_GATT_GET_CONN_ID(conn_id)) {
gatt_conn = &gatt_conns[i];
break;
}
}
}
return gatt_conn;
}
/* Map an ACL conn_handle to the BTA conn_id for BTA_GATT[CS]_* calls. Returns
* false if the link is already torn down (slot gone) — caller skips the BTA op. */
static bool gatt_conn_id_lookup(uint16_t conn_handle, uint16_t *conn_id)
{
struct gatt_conn *gatt_conn;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(conn_handle);
if (gatt_conn == NULL) {
return false;
}
*conn_id = gatt_conn->conn_id;
return true;
}
static void reset_gatt_conn(struct gatt_conn *gatt_conn)
{
gattc_list_drain(gatt_conn);
gatts_list_drain(gatt_conn);
gatts_notify_list_drain(gatt_conn);
free(gatt_conn->prep_buf); /* free(NULL) is safe when no long write was in flight */
memset(gatt_conn, 0, sizeof(*gatt_conn));
gatt_conn->conn_handle = UINT16_MAX;
}
@@ -392,7 +449,8 @@ static void gattc_connect_event_handler(tBTA_GATTC_CONNECT *connect)
qev->type = BT_LE_GATTC_CONNECT_EVENT;
qev->gattc_connect.conn_handle = BTC_GATT_GET_CONN_ID(connect->conn_id);
qev->gattc_connect.conn_handle = connect->conn_handle;
qev->gattc_connect.conn_id = connect->conn_id;
qev->gattc_connect.role = connect->link_role;
qev->gattc_connect.peer.type = connect->ble_addr_type;
memcpy(qev->gattc_connect.peer.val, connect->remote_bda, BT_ADDR_SIZE);
@@ -414,7 +472,7 @@ static void gattc_disconnect_event_handler(tBTA_GATTC_DISCONNECT *disconnect)
qev->type = BT_LE_GATTC_DISCONNECT_EVENT;
qev->gattc_disconnect.conn_handle = BTC_GATT_GET_CONN_ID(disconnect->conn_id);
qev->gattc_disconnect.conn_id = disconnect->conn_id;
qev->gattc_disconnect.reason = bta_disconn_reason_to_hci(disconnect->reason);
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
@@ -435,7 +493,7 @@ static void gattc_open_event_handler(tBTA_GATTC_OPEN *open)
qev->type = BT_LE_GATTC_OPEN_EVENT;
qev->gattc_open.status = open->status;
qev->gattc_open.conn_handle = BTC_GATT_GET_CONN_ID(open->conn_id);
qev->gattc_open.conn_id = open->conn_id;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
@@ -455,7 +513,7 @@ static void gattc_mtu_event_handler(tBTA_GATTC_CFG_MTU *cfg_mtu)
qev->type = BT_LE_GATTC_MTU_EVENT;
qev->gattc_mtu.status = cfg_mtu->status;
qev->gattc_mtu.conn_handle = BTC_GATT_GET_CONN_ID(cfg_mtu->conn_id);
qev->gattc_mtu.conn_id = cfg_mtu->conn_id;
qev->gattc_mtu.mtu = cfg_mtu->mtu;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
@@ -476,7 +534,7 @@ static void gattc_disc_cmpl_event_handler(tBTA_GATTC_DIS_CMPL *disc_cmpl)
qev->type = BT_LE_GATTC_DISC_CMPL_EVENT;
qev->gattc_disc_cmpl.status = disc_cmpl->status;
qev->gattc_disc_cmpl.conn_handle = BTC_GATT_GET_CONN_ID(disc_cmpl->conn_id);
qev->gattc_disc_cmpl.conn_id = disc_cmpl->conn_id;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
@@ -496,7 +554,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read)
qev->type = BT_LE_GATTC_READ_CHRC_EVENT;
qev->gattc_read_chrc.status = read->status;
qev->gattc_read_chrc.conn_handle = BTC_GATT_GET_CONN_ID(read->conn_id);
qev->gattc_read_chrc.conn_id = read->conn_id;
qev->gattc_read_chrc.attr_handle = read->handle;
if (read->p_value &&
@@ -531,7 +589,7 @@ static void gattc_write_chrc_event_handler(tBTA_GATTC_WRITE *write)
qev->type = BT_LE_GATTC_WRITE_CHRC_EVENT;
qev->gattc_write_chrc.status = write->status;
qev->gattc_write_chrc.conn_handle = BTC_GATT_GET_CONN_ID(write->conn_id);
qev->gattc_write_chrc.conn_id = write->conn_id;
qev->gattc_write_chrc.attr_handle = write->handle;
qev->gattc_write_chrc.offset = write->offset;
@@ -558,7 +616,7 @@ static void gatts_notify_tx_event_handler(tBTA_GATTS_REQ *req)
* list first (see gatts_notify_enqueue / handle_gatts_notify_tx_event),
* so is_notify here is left unused on this path. */
qev->gatts_notify_tx.is_notify = false;
qev->gatts_notify_tx.conn_handle = BTC_GATT_GET_CONN_ID(req->conn_id);
qev->gatts_notify_tx.conn_id = req->conn_id;
qev->gatts_notify_tx.attr_handle = req->handle;
qev->gatts_notify_tx.status = req->status;
@@ -580,7 +638,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify)
qev->type = BT_LE_GATTC_NOTIFY_RX_EVENT;
qev->gattc_notify_rx.is_notify = notify->is_notify;
qev->gattc_notify_rx.conn_handle = BTC_GATT_GET_CONN_ID(notify->conn_id);
qev->gattc_notify_rx.conn_id = notify->conn_id;
qev->gattc_notify_rx.attr_handle = notify->handle;
if (notify->len) {
@@ -612,7 +670,8 @@ static void gatts_connect_event_handler(tBTA_GATTS_CONN *connect)
qev->type = BT_LE_GATTS_CONNECT_EVENT;
qev->gatts_connect.conn_handle = BTC_GATT_GET_CONN_ID(connect->conn_id);
qev->gatts_connect.conn_handle = connect->conn_handle;
qev->gatts_connect.conn_id = connect->conn_id;
qev->gatts_connect.role = connect->link_role;
qev->gatts_connect.peer.type = connect->ble_addr_type;
memcpy(qev->gatts_connect.peer.val, connect->remote_bda, BT_ADDR_SIZE);
@@ -634,7 +693,7 @@ static void gatts_disconnect_event_handler(tBTA_GATTS_CONN *disconnect)
qev->type = BT_LE_GATTS_DISCONNECT_EVENT;
qev->gatts_disconnect.conn_handle = BTC_GATT_GET_CONN_ID(disconnect->conn_id);
qev->gatts_disconnect.conn_id = disconnect->conn_id;
qev->gatts_disconnect.reason = bta_disconn_reason_to_hci(disconnect->reason);
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
@@ -655,7 +714,7 @@ static void gatts_mtu_event_handler(tBTA_GATTS_REQ *req)
qev->type = BT_LE_GATTS_MTU_EVENT;
qev->gatts_mtu.conn_handle = BTC_GATT_GET_CONN_ID(req->conn_id);
qev->gatts_mtu.conn_id = req->conn_id;
qev->gatts_mtu.mtu = req->p_data->mtu;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
@@ -675,7 +734,7 @@ static void gatts_read_req_handler(tBTA_GATTS_REQ *req)
qev->type = BT_LE_GATTS_READ_EVENT;
qev->gatts_read.conn_handle = BTC_GATT_GET_CONN_ID(req->conn_id);
qev->gatts_read.conn_id = req->conn_id;
qev->gatts_read.trans_id = req->trans_id;
memcpy(qev->gatts_read.peer, req->remote_bda, BT_ADDR_SIZE);
qev->gatts_read.attr_handle = req->p_data->read_req.handle;
@@ -701,7 +760,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
qev->type = BT_LE_GATTS_WRITE_EVENT;
qev->gatts_write.conn_handle = BTC_GATT_GET_CONN_ID(req->conn_id);
qev->gatts_write.conn_id = req->conn_id;
qev->gatts_write.trans_id = req->trans_id;
memcpy(qev->gatts_write.peer, req->remote_bda, BT_ADDR_SIZE);
qev->gatts_write.attr_handle = req->p_data->write_req.handle;
@@ -728,6 +787,27 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
}
}
static void gatts_exec_write_req_handler(tBTA_GATTS_REQ *req)
{
struct bt_le_gatt_event_param *qev;
int err;
qev = calloc(1, sizeof(*qev));
assert(qev);
qev->type = BT_LE_GATTS_EXEC_WRITE_EVENT;
qev->gatts_exec_write.conn_id = req->conn_id;
qev->gatts_exec_write.trans_id = req->trans_id;
qev->gatts_exec_write.exec = req->p_data &&
req->p_data->exec_write == GATT_PREP_WRITE_EXEC;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_GATT_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("[B]GattsExecPostFail[%d]", err);
free(qev);
}
}
static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
{
switch (event) {
@@ -774,12 +854,6 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
break;
}
if (BTC_GATT_GET_CONN_ID(p_data->conn.conn_id) != p_data->conn.conn_handle) {
LOG_ERR("[B]GattsConnHdlMismatch[%u][%u]",
BTC_GATT_GET_CONN_ID(p_data->conn.conn_id), p_data->conn.conn_handle);
break;
}
gatts_connect_event_handler(&p_data->conn);
break;
@@ -983,6 +1057,14 @@ static void gatts_app_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data)
if (p_data->req_data.p_data) {
LOG_DBG("[B]GattsExecFlag[%u]", p_data->req_data.p_data->exec_write);
}
if (BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id) != gatts_if) {
LOG_ERR("[B]GattsExecUnknownIf[%u]",
BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id));
break;
}
gatts_exec_write_req_handler(&p_data->req_data);
break;
case BTA_GATTS_CONF_EVT:
@@ -1059,12 +1141,6 @@ static void gattc_app_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
break;
}
if (BTC_GATT_GET_CONN_ID(p_data->connect.conn_id) != p_data->connect.conn_handle) {
LOG_ERR("[B]GattcConnHdlMismatch[%u][%u]",
BTC_GATT_GET_CONN_ID(p_data->connect.conn_id), p_data->connect.conn_handle);
break;
}
gattc_connect_event_handler(&p_data->connect);
break;
@@ -1375,6 +1451,7 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
gatt_conn->status = 0x00;
gatt_conn->gatt_if = gattc_if;
gatt_conn->conn_handle = event->conn_handle;
gatt_conn->conn_id = event->conn_id;
gatt_conn->role = event->role;
gatt_conn->peer.type = event->peer.type;
memcpy(gatt_conn->peer.val, event->peer.val, BT_ADDR_SIZE);
@@ -1387,12 +1464,14 @@ static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *e
struct bt_conn *conn;
sys_snode_t *snode;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
if (gatt_conn == NULL) {
LOG_DBG("[B]GattcDisconnUnknownDev");
return;
}
event->conn_handle = gatt_conn->conn_handle;
if (gatt_conn->role == BTM_ROLE_MASTER) {
/* CENTRAL may also run a GATT server (e.g. CAP Initiator with PACS).
* Mirror handle_gatts_disconnect_event: fire func(err) for every
@@ -1419,12 +1498,14 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
struct gatt_conn *gatt_conn;
uint16_t conn_id;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_ERR("[B]GattcOpenUnknownDev");
return;
}
event->conn_handle = gatt_conn->conn_handle;
gatt_conn->status = event->status;
if (gatt_conn->role == BTM_ROLE_MASTER) {
@@ -1445,7 +1526,7 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
/* At this moment, the peer device may has not initiated MTU exchange */
if (gatt_conn->mtu == 0) {
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, gatt_conn->conn_handle);
conn_id = to_gattc_conn_id(gatt_conn->conn_id);
BTA_GATTC_ConfigureMTU(conn_id);
@@ -1465,12 +1546,14 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
{
struct gatt_conn *gatt_conn;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_ERR("[B]GattcMtuUnknownConn[%u]", event->conn_handle);
LOG_ERR("[B]GattcMtuUnknownConn[0x%04x]", event->conn_id);
return;
}
event->conn_handle = gatt_conn->conn_handle;
/* The device may works as GATT client or server or both, which is not
* related to the Link Layer role, hence we don't check the Link layer
* role here and only check if the MTU has already been exchanged here.
@@ -1499,15 +1582,41 @@ static void handle_gattc_disc_cmpl_event(struct bt_le_gattc_disc_cmpl_event *eve
{
struct gatt_conn *gatt_conn;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_ERR("[B]GattcDiscSvcUnknownConn[%u]", event->conn_handle);
LOG_ERR("[B]GattcDiscSvcUnknownConn[0x%04x]", event->conn_id);
return;
}
event->conn_handle = gatt_conn->conn_handle;
bt_le_gattc_app_disc_cmpl_event(event);
}
/* Map a BTA/Bluedroid GATT status to a standard ATT error. Bluedroid reuses the
* 0x80-0x92 range for internal failures (e.g. 0x85 on link loss) overlapping ATT
* app errors, so only codes the profiles receive OTA pass; the rest -> "unlikely". */
static uint8_t bluedroid_gattc_att_err(uint8_t status)
{
/* Success (0) and ATT spec error codes (0x01-0x1F) match the wire 1:1. */
if (status <= 0x1F) {
return status;
}
/* ATT application error codes the profiles receive as a client. 0x85+ here is
* Bluedroid-internal (GATT_ERROR etc.), not an over-the-air error. */
if (status >= 0x80 && status <= 0x84) {
return status;
}
/* Common Profile and Service error codes (0xFC-0xFF). */
if (status >= 0xFC) {
return status;
}
return BT_ATT_ERR_UNLIKELY;
}
static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *event)
{
struct bt_gatt_read_params read_copy;
@@ -1521,15 +1630,17 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
uint16_t off;
uint8_t ret;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattcRdCharNotConn");
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattcRdCharUnknownConn[0x%04x]", event->conn_id);
goto end;
}
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattcRdCharUnknownConn[%u]", event->conn_handle);
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattcRdCharNotConn");
goto end;
}
@@ -1582,7 +1693,13 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
vlen = (off < event->len) ? (event->len - off) : 0;
}
ret = read_copy.func(conn, event->status, params, val, vlen);
/* By-UUID read: report the matched handle in params->by_uuid.start_handle (like
* Zephyr); callers (has_client active-index read) use it as the value handle. */
if (read_copy.handle_count == 0 && event->status == 0) {
params->by_uuid.start_handle = event->attr_handle;
}
ret = read_copy.func(conn, bluedroid_gattc_att_err(event->status), params, val, vlen);
if (ret == BT_GATT_ITER_CONTINUE && event->status == 0) {
read_copy.func(conn, 0, params, NULL, 0);
@@ -1602,15 +1719,17 @@ static void handle_gattc_write_chrc_event(struct bt_le_gattc_write_chrc_event *e
struct bt_conn *conn;
sys_snode_t *snode;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattcWrCharNotConn");
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattcWrCharUnknownConn[0x%04x]", event->conn_id);
return;
}
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattcWrCharUnknownConn[%u]", event->conn_handle);
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattcWrCharNotConn");
return;
}
@@ -1647,7 +1766,7 @@ static void handle_gattc_write_chrc_event(struct bt_le_gattc_write_chrc_event *e
return;
}
params->func(conn, event->status, params);
params->func(conn, bluedroid_gattc_att_err(event->status), params);
}
static void handle_gattc_notify_event(struct bt_le_gattc_notify_rx_event *event)
@@ -1658,15 +1777,17 @@ static void handle_gattc_notify_event(struct bt_le_gattc_notify_rx_event *event)
struct gattc_sub *sub;
struct bt_conn *conn;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattcNotifNotConn");
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattcNotifUnknownConn[0x%04x]", event->conn_id);
goto end;
}
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattcNotifUnknownConn[%u]", event->conn_handle);
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattcNotifNotConn");
goto end;
}
@@ -1730,10 +1851,20 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event)
gatt_conn->status = 0x00;
gatt_conn->gatt_if = gatts_if;
gatt_conn->conn_handle = event->conn_handle;
gatt_conn->conn_id = event->conn_id;
gatt_conn->role = event->role;
gatt_conn->peer.type = event->peer.type;
memcpy(gatt_conn->peer.val, event->peer.val, BT_ADDR_SIZE);
/* conn->le.dst is the connect-event address verbatim — no RPA->identity resolution.
* A peer using an RPA makes bt_le_bond_exists()/CCC cfg miss on reconnect (fresh RPA
* each time). Identity / static-random / public addresses are stable. Warn to surface it. */
if (event->peer.type == BT_ADDR_LE_RANDOM && BT_ADDR_IS_RPA(&event->peer)) {
LOG_WRN("[B]GattsConnRpaDst[%02x:%02x:%02x:%02x:%02x:%02x]",
event->peer.val[5], event->peer.val[4], event->peer.val[3],
event->peer.val[2], event->peer.val[1], event->peer.val[0]);
}
post_acl_connect_app_event(gatt_conn);
}
@@ -1744,12 +1875,14 @@ static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *e
struct bt_conn *conn;
sys_snode_t *snode;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattsDisconnUnknownDev");
return;
}
event->conn_handle = gatt_conn->conn_handle;
/* Mirror handle_gatts_connect_event: SLAVE-side teardown happens here;
* MASTER teardown is in handle_gattc_disconnect_event. */
if (gatt_conn->role != BTM_ROLE_SLAVE) {
@@ -1783,12 +1916,14 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
{
struct gatt_conn *gatt_conn;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_ERR("[B]GattsMtuUnknownConn[%u]", event->conn_handle);
LOG_ERR("[B]GattsMtuUnknownConn[0x%04x]", event->conn_id);
return;
}
event->conn_handle = gatt_conn->conn_handle;
/* The device may works as GATT client or server or both, which is not
* related to the Link Layer role, hence we don't check the Link layer
* role here and only check if the MTU has already been exchanged here.
@@ -1812,6 +1947,7 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
static void handle_gatts_read_event(struct bt_le_gatts_read_event *event)
{
struct gatt_conn *gatt_conn;
struct bt_gatt_attr *attr;
tBTA_GATT_STATUS status;
struct bt_conn *conn;
@@ -1822,6 +1958,14 @@ static void handle_gatts_read_event(struct bt_le_gatts_read_event *event)
status = BTA_GATT_OK;
rsp = NULL;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattsRdUnknownConn[0x%04x]", event->conn_id);
return;
}
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattsRdEvtNotConn");
@@ -1844,24 +1988,23 @@ static void handle_gatts_read_event(struct bt_le_gatts_read_event *event)
rsp->attr_value.handle = event->attr_handle;
if (attr->read) {
if (event->offset) {
LOG_WRN("[B]GattsRdEvtNotSupp");
/* Pass the request offset through so GATT Read Blob (long read) works: the read
* cb returns value[offset..], BTA caps each response at ATT_MTU-1. Needed for
* values larger than one PDU (e.g. a BASS Broadcast Receive State). */
ret = attr->read(conn, attr, (void *)rsp, GATT_MAX_ATTR_LEN, event->offset);
if (ret < 0) {
LOG_DBG("[B]GattsRdEvtErr[%u][%d]", event->attr_handle, ret);
status = GATT_REQ_NOT_SUPPORTED;
} else {
ret = attr->read(conn, attr, (void *)rsp, GATT_MAX_ATTR_LEN, 0);
if (ret < 0) {
LOG_DBG("[B]GattsRdEvtErr[%u][%d]", event->attr_handle, ret);
status = BT_GATT_ERR(ret);
}
status = BT_GATT_ERR(ret);
}
} else {
status = GATT_READ_NOT_PERMIT;
}
end:
conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn->handle);
/* Respond on the conn_id the request arrived on (event->conn_id): for a central-side
* server the slot's canonical conn_id is the GATTC one, which would be wrong here. */
conn_id = event->conn_id;
BTA_GATTS_SendRsp(conn_id, event->trans_id, status, rsp);
@@ -1870,9 +2013,92 @@ end:
}
}
/* Buffer one ATT prepare-write chunk and echo it; the reassembled value is applied on
* the execute event. Per-connection state (struct gatt_conn) so concurrent long writes
* don't collide. Used for control-point PDUs > ATT_MTU-3 (e.g. BASS Add Source). */
static void handle_gatts_prepare_write(struct bt_le_gatts_write_event *event)
{
struct gatt_conn *gatt_conn;
tBTA_GATT_STATUS status = BTA_GATT_OK;
uint16_t conn_id;
tBTA_GATTS_RSP *rsp;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_ERR("[B]GattsPrepNoConn[0x%04x]", event->conn_id);
return;
}
if (event->offset == 0) {
/* A new long write always restarts at offset 0; this also discards any
* sequence abandoned by a prior disconnect. */
gatt_conn->prep_active = true;
gatt_conn->prep_error = false;
gatt_conn->prep_attr_handle = event->attr_handle;
gatt_conn->prep_len = 0;
} else if (!gatt_conn->prep_active ||
gatt_conn->prep_attr_handle != event->attr_handle ||
gatt_conn->prep_len != event->offset) {
LOG_WRN("[B]GattsPrepBadOft[%u][%u]", event->offset, gatt_conn->prep_len);
gatt_conn->prep_error = true;
status = GATT_INVALID_OFFSET;
}
if (status == BTA_GATT_OK) {
uint32_t total = (uint32_t)event->offset + event->len;
if (total > GATTS_PREP_MAX_LEN) {
LOG_WRN("[B]GattsPrepQFull[%u]", (unsigned)total);
gatt_conn->prep_error = true;
status = GATT_PREPARE_Q_FULL;
} else if (total == 0) {
/* realloc(_,0) frees prep_buf and returns NULL → dangling ptr that
execute/disconnect would double-free. Release it explicitly. */
free(gatt_conn->prep_buf);
gatt_conn->prep_buf = NULL;
gatt_conn->prep_len = 0;
} else {
/* Grow the reassembly buffer to the write size on demand; realloc leaves
* the old buffer intact on failure. */
uint8_t *nb = realloc(gatt_conn->prep_buf, total);
if (nb == NULL) {
LOG_ERR("[B]GattsPrepNoMem[%u]", (unsigned)total);
gatt_conn->prep_error = true;
status = GATT_INSUF_RESOURCE;
} else {
gatt_conn->prep_buf = nb;
memcpy(gatt_conn->prep_buf + event->offset, event->value, event->len);
gatt_conn->prep_len = (uint16_t)total;
}
}
}
conn_id = event->conn_id;
if (status == BTA_GATT_OK) {
/* The prepare-write response must echo handle/offset/value. */
rsp = calloc(1, sizeof(*rsp));
assert(rsp);
rsp->attr_value.handle = event->attr_handle;
rsp->attr_value.offset = event->offset;
rsp->attr_value.len = event->len;
if (event->len) {
memcpy(rsp->attr_value.value, event->value, event->len);
}
BTA_GATTS_SendRsp(conn_id, event->trans_id, status, rsp);
free(rsp);
} else {
BTA_GATTS_SendRsp(conn_id, event->trans_id, status, NULL);
}
}
static void handle_gatts_write_event(struct bt_le_gatts_write_event *event)
{
struct bt_le_gatts_subscribe_event sub_event;
struct gatt_conn *gatt_conn;
struct bt_gatt_attr *attr;
tBTA_GATT_STATUS status;
struct bt_conn *conn;
@@ -1881,6 +2107,17 @@ static void handle_gatts_write_event(struct bt_le_gatts_write_event *event)
status = BTA_GATT_OK;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattsWrUnknownConn[0x%04x]", event->conn_id);
if (event->value) {
free(event->value);
}
return;
}
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattsWrEvtNotConn");
@@ -1890,6 +2127,15 @@ static void handle_gatts_write_event(struct bt_le_gatts_write_event *event)
return;
}
/* Long write: queue the chunk and defer the attr->write to the execute event. */
if (event->is_prep) {
handle_gatts_prepare_write(event);
if (event->value) {
free(event->value);
}
return;
}
attr = bt_gatts_find_attr_by_handle(event->attr_handle);
if (attr == NULL) {
LOG_ERR("[B]GattsWrAttrNotFound[%u]", event->attr_handle);
@@ -1953,7 +2199,7 @@ static void handle_gatts_write_event(struct bt_le_gatts_write_event *event)
end:
if (event->need_rsp) {
conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn->handle);
conn_id = event->conn_id;
BTA_GATTS_SendRsp(conn_id, event->trans_id, status, NULL);
}
@@ -1963,6 +2209,61 @@ end:
}
}
static void handle_gatts_exec_write_event(struct bt_le_gatts_exec_write_event *event)
{
tBTA_GATT_STATUS status = BTA_GATT_OK;
struct gatt_conn *gatt_conn;
struct bt_gatt_attr *attr;
struct bt_conn *conn;
uint16_t conn_id;
ssize_t ret;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]GattsExecUnknownConn[0x%04x]", event->conn_id);
return;
}
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_WRN("[B]GattsExecNotConn");
return;
}
if (!gatt_conn->prep_active) {
LOG_DBG("[B]GattsExecNoPrep");
} else {
if (!event->exec) {
LOG_DBG("[B]GattsExecCancel");
} else if (gatt_conn->prep_error) {
LOG_WRN("[B]GattsExecPrepErr");
status = GATT_INSUF_RESOURCE;
} else {
attr = bt_gatts_find_attr_by_handle(gatt_conn->prep_attr_handle);
if (attr == NULL) {
status = GATT_INVALID_HANDLE;
} else if (attr->write == NULL) {
status = GATT_WRITE_NOT_PERMIT;
} else {
ret = attr->write(conn, attr, gatt_conn->prep_buf, gatt_conn->prep_len, 0, 0);
if (ret < 0) {
LOG_DBG("[B]GattsExecWrErr[%u][%d]", gatt_conn->prep_attr_handle, (int)ret);
status = BT_GATT_ERR(ret);
}
}
}
free(gatt_conn->prep_buf);
gatt_conn->prep_buf = NULL;
gatt_conn->prep_active = false;
}
conn_id = event->conn_id;
BTA_GATTS_SendRsp(conn_id, event->trans_id, status, NULL);
}
static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *event)
{
struct gatts_list_node *n;
@@ -1973,15 +2274,17 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve
/* Find conn once up front. If the acl_conn was already torn down (race
* with a queued CLOSE_EVT behind this CONF_EVT), leave the head in the
* list — disconnect drain will fire its func with BT_ATT_ERR_UNLIKELY. */
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL) {
LOG_WRN("[B]NotifyTxNoConn[%u]", event->conn_handle);
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
LOG_WRN("[B]NotifyTxUnknownConn[0x%04x]", event->conn_id);
return;
}
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(event->conn_handle);
if (gatt_conn == NULL) {
LOG_WRN("[B]NotifyTxUnknownConn[%u]", event->conn_handle);
event->conn_handle = gatt_conn->conn_handle;
conn = bt_le_acl_conn_find(event->conn_handle);
if (conn == NULL) {
LOG_WRN("[B]NotifyTxNoConn[%u]", event->conn_handle);
return;
}
@@ -2022,7 +2325,7 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve
if (next_snode != NULL) {
struct gatts_list_node *next_n =
CONTAINER_OF(next_snode, struct gatts_list_node, node);
gatts_indicate_dispatch(conn, next_n);
gatts_indicate_dispatch(conn, gatt_conn->conn_id, next_n);
}
}
@@ -2108,6 +2411,10 @@ void bt_le_bluedroid_gatt_handle_event(uint8_t *data, size_t data_len)
handle_gatts_write_event(&param->gatts_write);
break;
case BT_LE_GATTS_EXEC_WRITE_EVENT:
handle_gatts_exec_write_event(&param->gatts_exec_write);
break;
case BT_LE_GATTS_NOTIFY_TX_EVENT:
handle_gatts_notify_tx_event(&param->gatts_notify_tx);
break;
@@ -2326,8 +2633,11 @@ static int gatts_notify(struct bt_conn *conn,
}
}
conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn->handle);
BTA_GATTS_HandleValueIndication(conn_id, data.handle, len,
if (!gatt_conn_id_lookup(conn->handle, &conn_id)) {
return -ENOTCONN;
}
BTA_GATTS_HandleValueIndication(to_gatts_conn_id(conn_id), data.handle, len,
(uint8_t *)value, need_cfm);
} else {
bt_conn_get_acl_conns(&conns, &conns_count);
@@ -2341,8 +2651,11 @@ static int gatts_notify(struct bt_conn *conn,
continue;
}
conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conns[i].handle);
BTA_GATTS_HandleValueIndication(conn_id, data.handle, len,
if (!gatt_conn_id_lookup(conns[i].handle, &conn_id)) {
continue;
}
BTA_GATTS_HandleValueIndication(to_gatts_conn_id(conn_id), data.handle, len,
(uint8_t *)value, need_cfm);
}
}
@@ -2383,7 +2696,7 @@ static int gatts_indicate_enqueue(struct bt_conn *conn,
sys_slist_append(&gatt_conn->gatts_list, &n->node);
if (was_empty) {
gatts_indicate_dispatch(conn, n);
gatts_indicate_dispatch(conn, gatt_conn->conn_id, n);
}
return 0;
@@ -2478,7 +2791,8 @@ int bt_le_bluedroid_gattc_disc_start(uint16_t conn_handle)
return 0;
}
static int gattc_disc_primary_svc(struct bt_conn *conn, struct bt_gatt_discover_params *params)
static int gattc_disc_primary_svc(struct bt_conn *conn, uint16_t conn_id,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_service_val svc = {0};
struct bt_uuid_16 svc_uuid = {0};
@@ -2486,7 +2800,6 @@ static int gattc_disc_primary_svc(struct bt_conn *conn, struct bt_gatt_discover_
btgatt_db_element_t *db = NULL;
tBT_UUID uuid = {0};
uint16_t count = 0;
uint16_t conn_id;
uint8_t ret;
if (params->uuid == NULL) {
@@ -2494,7 +2807,6 @@ static int gattc_disc_primary_svc(struct bt_conn *conn, struct bt_gatt_discover_
return -ENOTSUP;
}
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
bt_le_bluedroid_gatt_uuid_convert(params->uuid, &uuid);
BTA_GATTC_GetServiceWithUUID(conn_id, &uuid, &db, &count);
@@ -2562,7 +2874,8 @@ end:
return 0;
}
static int gattc_disc_included_svc(struct bt_conn *conn, struct bt_gatt_discover_params *params)
static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_include inc_svc = {0};
struct bt_uuid_16 svc_uuid = {0};
@@ -2570,10 +2883,8 @@ static int gattc_disc_included_svc(struct bt_conn *conn, struct bt_gatt_discover
btgatt_db_element_t *db = NULL;
tBT_UUID uuid = {0};
uint16_t count = 0;
uint16_t conn_id;
uint8_t ret;
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
if (params->uuid) {
bt_le_bluedroid_gatt_uuid_convert(params->uuid, &uuid);
}
@@ -2650,7 +2961,8 @@ end:
return 0;
}
static int gattc_disc_chrc(struct bt_conn *conn, struct bt_gatt_discover_params *params)
static int gattc_disc_chrc(struct bt_conn *conn, uint16_t conn_id,
struct bt_gatt_discover_params *params)
{
struct bt_uuid_16 chrc_uuid = {0};
struct bt_gatt_attr attr = {0};
@@ -2658,11 +2970,8 @@ static int gattc_disc_chrc(struct bt_conn *conn, struct bt_gatt_discover_params
btgatt_db_element_t *db = NULL;
tBT_UUID uuid = {0};
uint16_t count = 0;
uint16_t conn_id;
uint8_t ret;
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
/* Note:
* Increment start_handle with 1 here because, for example, if 2 characteristics
* are found with characteristic value handle equals to A and B. After handling
@@ -2747,7 +3056,8 @@ end:
return 0;
}
static int gattc_disc_chrc_desc(struct bt_conn *conn, struct bt_gatt_discover_params *params)
static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_attr attr = {0};
btgatt_db_element_t *db = NULL;
@@ -2756,7 +3066,6 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, struct bt_gatt_discover_pa
uint16_t chrc_handle;
tBT_UUID uuid = {0};
uint16_t count = 0;
uint16_t conn_id;
int err = 0;
assert(params);
@@ -2773,7 +3082,6 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, struct bt_gatt_discover_pa
return -EINVAL;
}
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
chrc_handle = params->sub_params->value_handle;
bt_le_bluedroid_gatt_uuid_convert(params->uuid, &uuid);
@@ -2856,16 +3164,16 @@ int bt_le_bluedroid_gattc_discover(struct bt_conn *conn, struct bt_gatt_discover
switch (params->type) {
case BT_GATT_DISCOVER_PRIMARY:
return gattc_disc_primary_svc(conn, params);
return gattc_disc_primary_svc(conn, to_gattc_conn_id(gatt_conn->conn_id), params);
case BT_GATT_DISCOVER_INCLUDE:
return gattc_disc_included_svc(conn, params);
return gattc_disc_included_svc(conn, to_gattc_conn_id(gatt_conn->conn_id), params);
case BT_GATT_DISCOVER_CHARACTERISTIC:
return gattc_disc_chrc(conn, params);
return gattc_disc_chrc(conn, to_gattc_conn_id(gatt_conn->conn_id), params);
case BT_GATT_DISCOVER_DESCRIPTOR:
return gattc_disc_chrc_desc(conn, params);
return gattc_disc_chrc_desc(conn, to_gattc_conn_id(gatt_conn->conn_id), params);
default:
LOG_ERR("[B]DiscTypeNotSupp[%u]", params->type);
@@ -2899,7 +3207,7 @@ int bt_le_bluedroid_gattc_read(struct bt_conn *conn, struct bt_gatt_read_params
sys_slist_append(&gatt_conn->gattc_list, &op->node);
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
conn_id = to_gattc_conn_id(gatt_conn->conn_id);
if (op->read.params_copy.handle_count == 0) {
LOG_INF("[B]RdByTypeReq[0x%04x]",
@@ -2946,7 +3254,7 @@ int bt_le_bluedroid_gattc_write(struct bt_conn *conn, struct bt_gatt_write_param
sys_slist_append(&gatt_conn->gattc_list, &op->node);
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
conn_id = to_gattc_conn_id(gatt_conn->conn_id);
BTA_GATTC_WriteCharValue(conn_id, params->handle, BTA_GATTC_TYPE_WRITE,
params->length, (void *)params->data,
@@ -2972,7 +3280,7 @@ int bt_le_bluedroid_gattc_write_without_rsp(struct bt_conn *conn, uint16_t handl
return -ENODEV;
}
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
conn_id = to_gattc_conn_id(gatt_conn->conn_id);
/* TODO: BTA_GATTC_WriteCharValue is void and silently drops the cmd on
* osi_malloc failure. NimBLE returns the host status here, so the API
@@ -2992,7 +3300,13 @@ int bt_le_bluedroid_gattc_write_ccc(struct bt_conn *conn, struct bt_gatt_subscri
uint16_t chrc_handle;
uint16_t conn_id;
conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn->handle);
if (!gatt_conn_id_lookup(conn->handle, &conn_id)) {
LOG_INF("[B]NoConnInfo[%u]", conn->handle);
return -ENOTCONN;
}
conn_id = to_gattc_conn_id(conn_id);
chrc_handle = params->value_handle;
/* Unsubscribe path: caller (bt_gatt_unsubscribe) forces params->value to
@@ -20,6 +20,10 @@
extern "C" {
#endif
/* Reject ceiling for a reassembled ATT long write: the GATT attribute value max
* (0..512 octets). Costs no RAM — the buffer is malloc'd to the actual write size. */
#define GATTS_PREP_MAX_LEN 512
struct gatt_conn {
uint8_t used : 1;
uint8_t conn_create : 1;
@@ -30,6 +34,10 @@ struct gatt_conn {
uint8_t status;
uint8_t gatt_if;
uint16_t conn_handle;
/* BTA GATT conn_id: (internal GATT index << 8) | gatt_if. Diverges from
* conn_handle (the ACL handle) after connection churn, so it's captured
* verbatim from BTA at connect rather than rebuilt from the ACL handle. */
uint16_t conn_id;
uint8_t role;
struct {
uint8_t type;
@@ -54,6 +62,15 @@ struct gatt_conn {
* flag so the handler pops this list first to disambiguate from the
* indication acks tracked in gatts_list. See struct gatts_notify_node. */
sys_slist_t gatts_notify_list;
/* Per-connection ATT long-write (prepare/execute) reassembly, so concurrent long
* writes on different links never collide. prep_buf is malloc'd to the write size
* on demand and freed on execute/cancel/disconnect — idle links cost nothing. */
bool prep_active;
bool prep_error; /* sticky: a chunk overflowed or arrived out of order */
uint16_t prep_attr_handle;
uint16_t prep_len;
uint8_t *prep_buf;
};
uint8_t bt_le_bluedroid_gattc_get_if(void);
@@ -827,6 +827,14 @@ int bt_le_bluedroid_iso_cmd_send_sync(uint16_t opcode,
return bluedroid_err_to_errno(rc);
}
/* Strip the BTM_HCI_ERROR (0x80) flag Bluedroid ORs into tBTM_STATUS for controller
* HCI errors; the Zephyr host expects a raw HCI code. No-op on success. (Else a CIS
* failure surfaces as e.g. 0x9E instead of the real 0x1E.) */
static inline uint8_t iso_hci_status(uint8_t btm_status)
{
return btm_status & ~(uint8_t)BTM_HCI_ERROR;
}
static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *params)
{
enum iso_queue_item_type q_type;
@@ -870,7 +878,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata = calloc(1, qdata_len);
assert(qdata);
ev.status = params->btm_cis_established_evt.status;
ev.status = iso_hci_status(params->btm_cis_established_evt.status);
ev.conn_handle = params->btm_cis_established_evt.conn_handle;
sys_put_le24(params->btm_cis_established_evt.cig_sync_delay, ev.cig_sync_delay);
sys_put_le24(params->btm_cis_established_evt.cis_sync_delay, ev.cis_sync_delay);
@@ -920,7 +928,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata = calloc(1, qdata_len);
assert(qdata);
ev.status = params->btm_big_cmpl.status;
ev.status = iso_hci_status(params->btm_big_cmpl.status);
ev.big_handle = params->btm_big_cmpl.big_handle;
sys_put_le24(params->btm_big_cmpl.big_sync_delay, ev.sync_delay);
sys_put_le24(params->btm_big_cmpl.transport_latency, ev.latency);
@@ -968,7 +976,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata = calloc(1, qdata_len);
assert(qdata);
ev.status = params->btm_big_sync_estab.status;
ev.status = iso_hci_status(params->btm_big_sync_estab.status);
ev.big_handle = params->btm_big_sync_estab.big_handle;
sys_put_le24(params->btm_big_sync_estab.transport_latency_big, ev.latency);
ev.nse = params->btm_big_sync_estab.nse;
@@ -214,6 +214,27 @@ void bt_le_nimble_gap_post_event(void *param)
qev->security_change.bonded = desc.sec_state.bonded;
qev->security_change.dst.type = desc.peer_id_addr.type;
memcpy(qev->security_change.dst.val, desc.peer_id_addr.val, BT_ADDR_SIZE);
/* Capture the bonded LTK for the lib's CSIS SIRK encryption. The
* peripheral's own key (OUR_SEC) is the link LTK; fall back to the
* peer record. Mirrors nimble_desc_to_sec_level's store read. */
{
struct ble_store_value_sec sec = {0};
struct ble_store_key_sec skey = {0};
skey.peer_addr = desc.peer_id_addr;
if ((ble_store_read_our_sec(&skey, &sec) == 0 && sec.ltk_present) ||
(ble_store_read_peer_sec(&skey, &sec) == 0 && sec.ltk_present)) {
memcpy(qev->security_change.ltk, sec.ltk,
sizeof(qev->security_change.ltk));
qev->security_change.ltk_present = 1;
LOG_INF("[N]LtkFromStore[%u]", ev->enc_change.conn_handle);
} else {
/* Encrypted but no stored LTK; CSIS SIRK encryption will
* have no key. */
LOG_WRN("[N]NoLtkForEnc[%u]", ev->enc_change.conn_handle);
}
}
}
break;
@@ -169,8 +169,13 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
LOG_WRN("[N]GattcNrpStatus[%04x]", error->status);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_BY_UUID, read_params, 0);
if ((error->status & BLE_HS_ERR_ATT_BASE) && !iter_stopped) {
read_params->func(conn, (uint8_t)error->status, read_params, NULL, 0);
if (!iter_stopped) {
/* A NimBLE ATT error carries the wire code (BLE_HS_ATT_ERR base);
* anything else is an internal/link failure -> generic "unlikely". */
uint8_t att_err = (error->status & BLE_HS_ERR_ATT_BASE) ?
(uint8_t)error->status : BT_ATT_ERR_UNLIKELY;
read_params->func(conn, att_err, read_params, NULL, 0);
}
rc = error->status;
@@ -253,8 +258,13 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
LOG_WRN("[N]GattcNrpStatus[%04x]", error->status);
bt_le_nimble_gatt_nrp_remove(conn, GATTC_NRP_READ_LONG, read_params, 0);
if ((error->status & BLE_HS_ERR_ATT_BASE) && !iter_stopped) {
read_params->func(conn, (uint8_t)error->status, read_params, NULL, 0);
if (!iter_stopped) {
/* A NimBLE ATT error carries the wire code (BLE_HS_ATT_ERR base);
* anything else is an internal/link failure -> generic "unlikely". */
uint8_t att_err = (error->status & BLE_HS_ERR_ATT_BASE) ?
(uint8_t)error->status : BT_ATT_ERR_UNLIKELY;
read_params->func(conn, att_err, read_params, NULL, 0);
}
rc = error->status;
@@ -323,9 +333,10 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
default:
LOG_WRN("[N]GattcNrpStatus[%04x]", error->status);
if (error->status & BLE_HS_ERR_ATT_BASE) {
func(conn, (uint8_t)error->status, original, NULL, 0);
}
/* A NimBLE ATT error carries the wire code (BLE_HS_ATT_ERR base);
* anything else is an internal/link failure -> generic "unlikely". */
func(conn, (error->status & BLE_HS_ERR_ATT_BASE) ?
(uint8_t)error->status : BT_ATT_ERR_UNLIKELY, original, NULL, 0);
rc = error->status;
break;
@@ -430,9 +441,10 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
default:
LOG_WRN("[N]GattcNrpStatus[%04x]", error->status);
if (error->status & BLE_HS_ERR_ATT_BASE) {
write_params->func(conn, (uint8_t)error->status, write_params);
}
/* A NimBLE ATT error carries the wire code (BLE_HS_ATT_ERR base);
* anything else is an internal/link failure -> generic "unlikely". */
write_params->func(conn, (error->status & BLE_HS_ERR_ATT_BASE) ?
(uint8_t)error->status : BT_ATT_ERR_UNLIKELY, write_params);
rc = error->status;
break;
@@ -43,11 +43,11 @@ static int ots_l2cap_recv_ready(struct ble_l2cap_chan *chan)
struct os_mbuf *sdu_rx;
int rc;
LOG_DBG("[N]OtsRecvReady");
LOG_DBG("[N]L2capOtsRecvReady");
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_rx == NULL) {
LOG_ERR("[N]NoBufForL2capRecv");
LOG_ERR("[N]L2capNoBufForL2capRecv");
return -ENOMEM;
}
@@ -70,31 +70,31 @@ static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
uint8_t *sdu;
int err;
LOG_DBG("[N]OtsEvtCb[%u]", event->type);
LOG_DBG("[N]L2capOtsEvtCb[%u]", event->type);
switch (event->type) {
case BLE_L2CAP_EVENT_COC_CONNECTED:
if (event->connect.status) {
LOG_ERR("[N]CocConnectFail[%d]", event->connect.status);
LOG_ERR("[N]L2capCocConnectFail[%d]", event->connect.status);
return 0;
}
if (ots_chan) {
LOG_ERR("[N]CocChanExist");
LOG_ERR("[N]L2capCocChanExist");
return 0;
}
ots_chan = event->connect.chan;
if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) {
LOG_ERR("[N]CocGetChanInfoFail");
LOG_ERR("[N]L2capCocGetChanInfoFail");
/* Roll back the latch so the next COC_CONNECTED isn't refused
* by the if (ots_chan) guard above. */
ots_chan = NULL;
return -EIO;
}
LOG_DBG("[N]CocConnect[%u][%04x][%04x][%04x][%u][%u][%u][%u]",
LOG_INF("[N]L2capCocConnect[%u][%04x][%04x][%04x][%u][%u][%u][%u]",
event->connect.conn_handle, chan_info.scid, chan_info.dcid,
chan_info.psm, chan_info.our_l2cap_mtu, chan_info.peer_l2cap_mtu,
chan_info.our_coc_mtu, chan_info.peer_coc_mtu);
@@ -106,11 +106,11 @@ static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
if (ots_chan != event->disconnect.chan) {
LOG_ERR("[N]DisconnectInvCocChan");
LOG_ERR("[N]L2capDisconnectInvCocChan");
return 0;
}
LOG_DBG("[N]CocDisconnect[%u][%04x]",
LOG_INF("[N]L2capCocDisconnect[%u][%04x]",
event->disconnect.conn_handle, event->disconnect.chan->psm);
bt_le_l2cap_disconnected(event->disconnect.conn_handle, event->disconnect.chan->psm);
@@ -119,13 +119,11 @@ static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
return 0;
case BLE_L2CAP_EVENT_COC_ACCEPT:
if (event->accept.peer_sdu_size > L2CAP_LE_OTS_MTU) {
LOG_ERR("[N]InvAcceptMtu[%u][%u]",
event->accept.peer_sdu_size, L2CAP_LE_OTS_MTU);
return -EINVAL;
}
/* Don't reject on peer_sdu_size: LE CoC allows asymmetric MTUs. It's the peer's
* RX MTU (our TX ceiling) — harmless; our RX is bounded by ble_l2cap_create_server.
* (PTS uses 1024 > our 256; rejecting broke SCP.) */
LOG_DBG("[N]CocAccept[%u][%04x][%04x][%u][%u][%u][%u]",
LOG_DBG("[N]L2capCocAccept[%u][%04x][%04x][%u][%u][%u][%u]",
event->accept.conn_handle, event->accept.chan->psm,
event->accept.chan->dcid, event->accept.chan->coc_tx.mtu,
event->accept.chan->peer_coc_mps, event->accept.peer_sdu_size,
@@ -148,13 +146,13 @@ static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
if (ots_chan != event->receive.chan) {
LOG_ERR("[N]RecvOnInvCocChan");
LOG_ERR("[N]L2capRecvOnInvCocChan");
return 0;
}
assert(event->receive.sdu_rx);
LOG_DBG("[N]CocReceive[%u][%04x][%u]",
LOG_DBG("[N]L2capCocRecv[%u][%04x][%u]",
event->receive.conn_handle, event->receive.chan->psm,
event->receive.sdu_rx->om_len);
@@ -177,11 +175,11 @@ static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED:
if (ots_chan != event->tx_unstalled.chan) {
LOG_ERR("[N]TxUnstalledOnInvCocChan");
LOG_ERR("[N]L2capTxUnstalledOnInvCocChan");
return 0;
}
LOG_DBG("[N]CocTxUnstalled[%u][%d]",
LOG_WRN("[N]L2capCocTxUnstalled[%u][%d]",
event->tx_unstalled.conn_handle, event->tx_unstalled.status);
/* TODO: transmit the remaining data */
@@ -198,13 +196,13 @@ int bt_le_nimble_l2cap_chan_connect(uint16_t conn_handle)
int rc;
if (ots_chan) {
LOG_WRN("[N]OtsChanExist");
LOG_WRN("[N]L2capOtsChanExist");
return -EALREADY;
}
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_rx == NULL) {
LOG_ERR("[N]NoBufForL2capConnect");
LOG_ERR("[N]L2capNoBufForConnect");
return -ENOMEM;
}
@@ -225,12 +223,12 @@ int bt_le_nimble_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
int rc;
if (ots_chan == NULL) {
LOG_WRN("[N]NoOtsChan");
LOG_WRN("[N]L2capNoOtsChan");
return -ENOTCONN;
}
if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) {
LOG_ERR("[N]UnexpOtsChan[%u][%u]",
LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]",
ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle);
return -EINVAL;
}
@@ -250,25 +248,25 @@ int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf
int rc;
if (ots_chan == NULL) {
LOG_WRN("[N]NoOtsChan");
LOG_WRN("[N]L2capNoOtsChan");
return -ENOTCONN;
}
if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) {
LOG_ERR("[N]UnexpOtsChan[%u][%u]",
LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]",
ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle);
return -EINVAL;
}
sdu_tx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_tx == NULL) {
LOG_ERR("[N]NoBufForL2capSend");
LOG_ERR("[N]L2capNoBufForSend");
return -ENOMEM;
}
rc = os_mbuf_append(sdu_tx, buf->data, buf->len);
if (rc) {
LOG_ERR("[N]AppendBufFail[%d]", rc);
LOG_ERR("[N]L2capAppendBufFail[%d]", rc);
os_mbuf_free_chain(sdu_tx);
return -EIO;
}
@@ -279,19 +277,23 @@ int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf
/* sdu is queued in tx->sdus[0]; NimBLE will continue on
* BLE_L2CAP_EVENT_COC_TX_UNSTALLED. Do NOT free here.
*/
LOG_WRN("[N]MoreCreditsForL2capSend");
LOG_WRN("[N]L2capMoreCreditsForSend");
} else if (rc == BLE_HS_EBADDATA || rc == BLE_HS_EBUSY) {
/* sdu was rejected before being queued; caller still owns it. */
LOG_ERR("[N]L2capSendFail[%d]", rc);
os_mbuf_free_chain(sdu_tx);
} else {
/* Internal error inside continue_tx; NimBLE already freed sdu. */
LOG_ERR("[N]L2capSendFail[%d]", rc);
LOG_ERR("[N]L2capSendInternalFail[%d]", rc);
}
return nimble_err_to_errno(rc);
}
/* Payload copied into sdu_tx; consume buf here (no async TX-done to unref it),
* else the 1-buffer ot_chan_tx_pool leaks. Failure paths leave buf to caller. */
net_buf_unref(buf);
return 0;
}
@@ -301,20 +303,20 @@ int bt_le_nimble_l2cap_init(void)
rc = os_mempool_init(&ots_mbuf_mempool, OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2, ots_mem, "ots_pool");
if (rc) {
LOG_ERR("[N]InitOtsMempoolFail[%d]", rc);
LOG_ERR("[N]L2capInitOtsMempoolFail[%d]", rc);
return rc;
}
rc = os_mbuf_pool_init(&ots_mbuf_pool, &ots_mbuf_mempool, L2CAP_LE_OTS_MTU, OTS_L2CAP_BUF_COUNT);
if (rc) {
LOG_ERR("[N]InitOtsMbufPoolFail[%d]", rc);
LOG_ERR("[N]L2capInitOtsMbufPoolFail[%d]", rc);
return rc;
}
#if CONFIG_BT_OTS
rc = ble_l2cap_create_server(L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU, ots_l2cap_event_cb, NULL);
if (rc) {
LOG_ERR("[N]CreateL2capSrvFail[%d]", rc);
LOG_ERR("[N]L2capCreateL2capSrvFail[%d]", rc);
return rc;
}
#endif /* CONFIG_BT_OTS */
+8 -1
View File
@@ -70,7 +70,8 @@ struct bt_le_ext_adv *bt_le_ext_adv_find(uint8_t adv_handle)
}
_IDF_ONLY
int bt_le_ext_adv_new_safe(uint8_t adv_handle)
int bt_le_ext_adv_new_safe(uint8_t adv_handle, uint8_t addr_type,
const uint8_t *addr, uint8_t sid)
{
struct bt_le_ext_adv *adv = NULL;
int err = 0;
@@ -92,6 +93,9 @@ int bt_le_ext_adv_new_safe(uint8_t adv_handle)
}
adv->handle = adv_handle;
adv->addr.type = addr_type;
bt_addr_copy(&adv->addr.a, (const bt_addr_t *)addr);
adv->sid = sid;
end:
bt_le_host_unlock();
@@ -136,6 +140,9 @@ int bt_le_ext_adv_get_info(const struct bt_le_ext_adv *adv,
return -EINVAL;
}
info->sid = adv->sid;
info->addr = &adv->addr;
/* Force to use ENABLED state for LIB usage */
info->ext_adv_state = BT_LE_EXT_ADV_STATE_ENABLED;
info->per_adv_state = BT_LE_PER_ADV_STATE_ENABLED;
@@ -400,6 +400,17 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
assert(err == 0);
}
/* Point conn->le.keys at the bonded LTK the adapter captured, so the lib's
* CSIS SIRK encryption can read it. conn exists here (found or just made). */
if (param->security_change.ltk_present) {
conn = bt_le_acl_conn_find(event.security_change.conn_handle);
if (conn != NULL) {
bt_conn_le_set_ltk(conn, param->security_change.ltk);
} else {
LOG_WRN("SecChgLtkNoConn[%u]", event.security_change.conn_handle);
}
}
err = bt_le_acl_conn_security_changed_listener(event.security_change.conn_handle,
event.security_change.sec_level);
assert(err == 0);
@@ -25,6 +25,11 @@ LOG_MODULE_REGISTER(ISO_CONN, CONFIG_BT_ISO_LOG_LEVEL);
static struct bt_conn acl_conns[CONFIG_BT_MAX_CONN];
/* Per-ACL LTK backing store (indexed in lockstep with acl_conns[]). conn->le.keys is
* a bare pointer the adapters fill after bonding; point it at the matching slot so the
* lib's CSIS sirk_encrypt can read conn->le.keys->ltk.val. */
static struct bt_keys conn_ltk[CONFIG_BT_MAX_CONN];
extern struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN];
static sys_slist_t conn_cbs = SYS_SLIST_STATIC_INIT(&conn_cbs);
@@ -386,6 +391,33 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
return (conn ? 0 : -ENOMEM);
}
/* Point conn->le.keys at this ACL connection's LTK slot, filled with the bonded
* LTK the adapter captured. Used as key K by the lib's CSIS SIRK encryption. */
_IDF_ONLY
void bt_conn_le_set_ltk(struct bt_conn *conn, const uint8_t *ltk)
{
size_t idx;
if (conn == NULL || ltk == NULL) {
LOG_ERR("ConnSetLtkBadArg");
return;
}
/* Only ACL (LE) connections carry an LTK; the pool tracks acl_conns[]. */
if (conn < acl_conns || conn >= &acl_conns[ARRAY_SIZE(acl_conns)]) {
LOG_WRN("ConnSetLtkNotAcl");
return;
}
idx = (size_t)(conn - acl_conns);
memset(&conn_ltk[idx], 0, sizeof(conn_ltk[idx]));
memcpy(conn_ltk[idx].ltk.val, ltk, sizeof(conn_ltk[idx].ltk.val));
conn->le.keys = &conn_ltk[idx];
LOG_INF("ConnSetLtk[%u][%s]", conn->handle, bt_hex(ltk, 16));
}
_IDF_ONLY
int bt_le_acl_conn_delete(uint16_t conn_handle)
{
@@ -400,6 +432,10 @@ int bt_le_acl_conn_delete(uint16_t conn_handle)
return -ENOTCONN;
}
/* Wipe this connection's LTK slot (key hygiene); the memset below then nulls
* conn->le.keys. */
memset(&conn_ltk[conn - acl_conns], 0, sizeof(conn_ltk[0]));
memset(conn, 0, sizeof(struct bt_conn));
return 0;
@@ -685,5 +721,9 @@ int bt_le_acl_conn_bond_deleted_listener(uint8_t id, const bt_addr_le_t *peer)
}
}
/* Profiles above cleared their own per-client state; also drop the peer's
* retained server CCC cfg, which is only kept while the bond exists. */
bt_le_acl_conn_bond_deleted_gatt_listener(id, peer);
return 0;
}
+215 -63
View File
@@ -19,6 +19,7 @@
#include <zephyr/bluetooth/common/bt_str.h>
#include <../host/conn_internal.h>
#include <../host/hci_core.h>
#include "common/host.h"
@@ -28,7 +29,53 @@ static sys_slist_t gatt_db = SYS_SLIST_STATIC_INIT(&gatt_db);
static struct gattc_sub subscriptions[CONFIG_BT_MAX_CONN];
/* Each CCC descriptor's cfg is a heap pool (depth BT_GATT_CCC_MAX = bonded peers +
* active conn), allocated here at registration; freed in gatts_free_svc_ccc_cfg. */
_LIB_ONLY
static int gatts_alloc_svc_ccc_cfg(struct bt_gatt_service *svc)
{
for (size_t i = 0; i < svc->attr_count; i++) {
const struct bt_gatt_attr *attr = &svc->attrs[i];
struct bt_gatt_ccc_managed_user_data *ccc;
if (attr->uuid->type != BT_UUID_TYPE_16 ||
BT_UUID_16(attr->uuid)->val != BT_UUID_GATT_CCC_VAL) {
continue;
}
ccc = attr->user_data;
ccc->cfg = calloc(BT_GATT_CCC_MAX, sizeof(struct bt_gatt_ccc_cfg));
if (ccc->cfg == NULL) {
return -ENOMEM;
}
ccc->cfg_count = BT_GATT_CCC_MAX;
}
return 0;
}
static void gatts_free_svc_ccc_cfg(struct bt_gatt_service *svc)
{
for (size_t i = 0; i < svc->attr_count; i++) {
const struct bt_gatt_attr *attr = &svc->attrs[i];
struct bt_gatt_ccc_managed_user_data *ccc;
if (attr->uuid->type != BT_UUID_TYPE_16 ||
BT_UUID_16(attr->uuid)->val != BT_UUID_GATT_CCC_VAL) {
continue;
}
ccc = attr->user_data;
if (ccc->cfg != NULL) {
free(ccc->cfg);
ccc->cfg = NULL;
}
ccc->cfg_count = 0;
ccc->value = 0; /* else a re-registered svc keeps the old subscription
state and suppresses the next cfg_changed. */
}
}
int bt_gatt_service_register(struct bt_gatt_service *svc)
{
const struct bt_uuid_16 *uuid;
@@ -52,6 +99,13 @@ int bt_gatt_service_register(struct bt_gatt_service *svc)
sys_slist_append(&gatt_db, &svc->node);
if (gatts_alloc_svc_ccc_cfg(svc) != 0) {
LOG_ERR("GattSvcCccAllocFail");
gatts_free_svc_ccc_cfg(svc);
(void)sys_slist_find_and_remove(&gatt_db, &svc->node);
return -ENOMEM;
}
return 0;
}
@@ -80,6 +134,8 @@ int bt_gatt_service_unregister(struct bt_gatt_service *svc)
return -ENOENT;
}
gatts_free_svc_ccc_cfg(svc);
return 0;
}
@@ -166,7 +222,7 @@ uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr)
*/
if (attr) {
LOG_DBG("GattAttrUuid[%s]", attr->uuid ? bt_uuid_str(attr->uuid) : "Null");
LOG_DBG("GattAttrValHdlUuid[%s]", attr->uuid ? bt_uuid_str(attr->uuid) : "Null");
if (attr->uuid == NULL) {
handle = (attr->handle + 1);
@@ -251,7 +307,7 @@ static uint8_t gatt_foreach_iter(const struct bt_gatt_attr *attr,
/* Match attribute user_data if set */
if (attr_data && attr_data != attr->user_data) {
LOG_DBG("MismatchAttrData");
LOG_DBG("GattForeachIterMismatchData");
return BT_GATT_ITER_CONTINUE;
}
@@ -260,11 +316,11 @@ static uint8_t gatt_foreach_iter(const struct bt_gatt_attr *attr,
result = func(attr, handle, user_data);
if (*num_matches == 0) {
LOG_DBG("NumMatchesZero");
LOG_DBG("GattForeachIterNumMatchesZero");
return BT_GATT_ITER_STOP;
}
LOG_DBG("GattIter%s", result == BT_GATT_ITER_CONTINUE ? "Cont" : "Stop");
LOG_DBG("GattForeachIter%s", result == BT_GATT_ITER_CONTINUE ? "Cont" : "Stop");
return result;
}
@@ -359,9 +415,9 @@ bool bt_gatts_find_attr_by_uuid(struct notify_data *found, const struct bt_uuid
bt_gatt_foreach_attr_type(found->handle, 0xffff, uuid, NULL, 1, match_uuid, found);
if (found->attr) {
LOG_DBG("AttrFoundByUuid");
LOG_DBG("GattsFindAttrByUuidFound");
} else {
LOG_WRN("AttrNotFoundByUuid");
LOG_WRN("GattsFindAttrByUuidNotFound");
}
return (found->attr ? true : false);
@@ -397,7 +453,7 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
LOG_DBG("GattAttrRd[%u][%u][%u]", buf_len, offset, value_len);
if (offset > value_len) {
LOG_ERR("TooLargeOffset[%u][%u]", offset, value_len);
LOG_ERR("GattAttrRdTooLargeOffset[%u][%u]", offset, value_len);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
@@ -454,12 +510,12 @@ int bt_gatt_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *param
}
if ((!params->data ^ !params->len)) {
LOG_ERR("InvData[%p][%u]", params->data, params->len);
LOG_ERR("GattIndInvData[%p][%u]", params->data, params->len);
return -EINVAL;
}
if (params->attr->uuid->type != BT_UUID_TYPE_16) {
LOG_ERR("InvUuidType[%u]", params->attr->uuid->type);
LOG_ERR("GattIndInvUuidType[%u]", params->attr->uuid->type);
return -ENOTSUP;
}
@@ -503,7 +559,7 @@ static uint8_t gatts_get_svc_handles(const struct bt_gatt_attr *attr, uint16_t h
/* Stop if attribute is a service */
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) == 0 ||
bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY) == 0) {
LOG_DBG("SvcAttr");
LOG_DBG("GattsSvcAttr");
return BT_GATT_ITER_STOP;
}
@@ -580,36 +636,53 @@ ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn,
memcpy(pdu.uuid, BT_UUID_128(chrc->uuid)->val, 16);
value_len += 16U;
} else {
LOG_ERR("UnsupportedUuidType[%u]", chrc->uuid->type);
LOG_ERR("GattAttrRdNotSupUuidType[%u]", chrc->uuid->type);
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}
static struct bt_gatt_ccc_cfg *gatts_find_ccc_cfg(const struct bt_conn *conn,
struct bt_gatt_ccc_managed_user_data *ccc)
static struct bt_gatt_ccc_cfg *gatts_find_free_ccc_cfg(struct bt_gatt_ccc_managed_user_data *ccc)
{
/* LOG_DBG("GattsFindCccCfg"); */
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
for (size_t i = 0; i < ccc->cfg_count; i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (conn) {
if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
/* LOG_DBG("CccCfgFound[%u]", i); */
return cfg;
}
} else if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
LOG_DBG("CccCfgNew[%u]", i);
if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
LOG_DBG("GattsCccCfgNew[%u]", i);
return cfg;
}
}
if (conn) {
/* LOG_DBG("CccCfgNotFound"); */
} else {
LOG_ERR("CccNoFreeCfg");
LOG_WRN("GattsCccNoFreeCfg");
return NULL;
}
static struct bt_gatt_ccc_cfg *gatts_find_ccc_cfg_by_conn(const struct bt_conn *conn,
struct bt_gatt_ccc_managed_user_data *ccc)
{
assert(conn);
for (size_t i = 0; i < ccc->cfg_count; i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
return cfg;
}
}
return NULL;
}
static struct bt_gatt_ccc_cfg *gatts_find_ccc_cfg_by_addr(const bt_addr_le_t *peer,
struct bt_gatt_ccc_managed_user_data *ccc)
{
for (size_t i = 0; i < ccc->cfg_count; i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (bt_addr_le_eq(&cfg->peer, peer)) {
return cfg;
}
}
return NULL;
@@ -620,7 +693,7 @@ static void gatts_ccc_changed(const struct bt_gatt_attr *attr,
{
uint16_t value = 0x0000;
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
for (size_t i = 0; i < ccc->cfg_count; i++) {
if (ccc->cfg[i].value > value) {
value = ccc->cfg[i].value;
}
@@ -645,6 +718,62 @@ static void gatts_clear_ccc_cfg(struct bt_gatt_ccc_cfg *cfg)
cfg->value = 0U;
}
static void gatts_clear_ccc_cfg_by_conn(struct bt_conn *conn)
{
/* Free this peer's CCC cfg entries. The pool is only BT_GATT_CCC_MAX deep, so a
* stale entry would starve a later different peer's subscribe (CccNoFreeCfg). */
struct bt_gatt_service *svc;
SYS_SLIST_FOR_EACH_CONTAINER(&gatt_db, svc, node) {
for (size_t i = 0; i < svc->attr_count; i++) {
const struct bt_gatt_attr *attr = &svc->attrs[i];
struct bt_gatt_ccc_managed_user_data *ccc;
struct bt_gatt_ccc_cfg *cfg;
if (attr->uuid->type != BT_UUID_TYPE_16 ||
BT_UUID_16(attr->uuid)->val != BT_UUID_GATT_CCC_VAL) {
continue;
}
ccc = attr->user_data;
cfg = gatts_find_ccc_cfg_by_conn(conn, ccc);
if (cfg != NULL) {
gatts_clear_ccc_cfg(cfg);
gatts_ccc_changed(attr, ccc);
}
}
}
}
static void gatts_clear_ccc_cfg_by_addr(const bt_addr_le_t *peer)
{
/* Address-keyed twin of gatts_clear_ccc_cfg_by_conn: bond-delete carries only
* the identity address, with no bt_conn to key on. */
struct bt_gatt_service *svc;
SYS_SLIST_FOR_EACH_CONTAINER(&gatt_db, svc, node) {
for (size_t i = 0; i < svc->attr_count; i++) {
const struct bt_gatt_attr *attr = &svc->attrs[i];
struct bt_gatt_ccc_managed_user_data *ccc;
struct bt_gatt_ccc_cfg *cfg;
if (attr->uuid->type != BT_UUID_TYPE_16 ||
BT_UUID_16(attr->uuid)->val != BT_UUID_GATT_CCC_VAL) {
continue;
}
ccc = attr->user_data;
cfg = gatts_find_ccc_cfg_by_addr(peer, ccc);
if (cfg != NULL) {
gatts_clear_ccc_cfg(cfg);
gatts_ccc_changed(attr, ccc);
}
}
}
}
_LIB_IDF
ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
@@ -662,7 +791,7 @@ ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn,
LOG_DBG("GattAttrRdCcc[%u][%u]", len, offset);
cfg = gatts_find_ccc_cfg(conn, ccc);
cfg = gatts_find_ccc_cfg_by_conn(conn, ccc);
if (cfg) {
value = sys_cpu_to_le16(cfg->value);
} else {
@@ -690,7 +819,7 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
ccc = attr->user_data;
assert(ccc);
LOG_DBG("GattAttrWrChrc[%u][%u][%02x]", len, offset, flags);
LOG_DBG("GattAttrWrCcc[%u][%u][%02x]", len, offset, flags);
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
@@ -708,18 +837,18 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
new_entry = false;
cfg = gatts_find_ccc_cfg(conn, ccc);
cfg = gatts_find_ccc_cfg_by_conn(conn, ccc);
if (cfg == NULL) {
/* If there's no existing entry, but the new value is zero,
* we don't need to do anything, since a disabled CCC is
* behaviorally the same as no written CCC.
*/
if (value == 0) {
LOG_INF("CccValZero");
LOG_INF("GattAttrWrCccValZero");
return len;
}
cfg = gatts_find_ccc_cfg(NULL, ccc);
cfg = gatts_find_free_ccc_cfg(ccc);
if (cfg == NULL) {
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
@@ -734,7 +863,7 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
if (ccc->cfg_write) {
ssize_t write = ccc->cfg_write(conn, attr, value);
LOG_INF("CccCfgWr[%d]", write);
LOG_INF("GattAttrWrCccCfgWr[%d]", write);
if (write < 0) {
if (new_entry) {
@@ -752,7 +881,7 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
}
}
LOG_DBG("CccCfg[%u][%04x][%04x]", attr->handle, cfg->value, value);
LOG_DBG("GattAttrWrCccCfg[%u][%04x][%04x]", attr->handle, cfg->value, value);
value_changed = (cfg->value != value);
cfg->value = value;
@@ -798,32 +927,32 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
ssize_t len;
if (attr->read == NULL) {
LOG_WRN("AttrRdCbNull");
LOG_WRN("GattIsSubAttrRdCbNull");
return false;
}
/* The characteristic properties is the first byte of the attribute value */
len = attr->read(NULL, attr, &properties, 1, 0);
if (len < 0) {
LOG_ERR("RdAttrFail[%zd]", len);
LOG_ERR("GattIsSubRdAttrFail[%zd]", len);
return false;
}
if (len != 1) {
LOG_ERR("InvRdLen[%zd]", len);
LOG_ERR("GattIsSubInvRdLen[%zd]", len);
return false;
}
if (!(properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) {
/* Characteristic doesn't support subscription */
LOG_WRN("SubNotSupp[%02x]", properties);
LOG_WRN("GattIsSubNotSupp[%02x]", properties);
return false;
}
attr = bt_gatt_attr_next(attr);
assert(attr && attr->uuid);
LOG_DBG("Attr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
}
/* Check if attribute is a characteristic value */
@@ -834,7 +963,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
}
assert(attr->uuid);
LOG_DBG("Attr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
}
/* Find the CCC Descriptor */
@@ -851,7 +980,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
assert(attr->uuid);
LOG_DBG("Attr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC)) {
return false;
@@ -861,10 +990,10 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
assert(ccc);
/* Check if the connection is subscribed */
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
for (size_t i = 0; i < ccc->cfg_count; i++) {
const struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
LOG_DBG("CccCfg[%u][%04x]", i, ccc->cfg[i].value);
LOG_DBG("GattIsSubCccCfg[%u][%04x]", i, ccc->cfg[i].value);
if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer) &&
(ccc_type & ccc->cfg[i].value)) {
@@ -888,18 +1017,18 @@ static int gatts_ccc_cfg_update(struct bt_conn *conn,
/* LOG_DBG("GattsCccCfgUpd[%04x]", value); */
cfg = gatts_find_ccc_cfg(conn, ccc);
cfg = gatts_find_ccc_cfg_by_conn(conn, ccc);
if (cfg == NULL) {
/* If there's no existing entry, and the new notification value
* is zero, we don't need to do anything, since a disabled CCC
* is behaviorally the same as no written CCC.
*/
if (value == 0) {
LOG_INF("CccValZero");
LOG_INF("GattsCccCfgUpdValZero");
return 0;
}
cfg = gatts_find_ccc_cfg(NULL, ccc);
cfg = gatts_find_free_ccc_cfg(ccc);
if (cfg == NULL) {
return -ENOMEM;
}
@@ -1014,7 +1143,7 @@ int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *param
}
if (params->uuid && params->uuid->type != BT_UUID_TYPE_16) {
LOG_ERR("InvUuid[%s]", bt_uuid_str(params->uuid));
LOG_ERR("GattDiscInvUuid[%s]", bt_uuid_str(params->uuid));
return -ENOTSUP;
}
@@ -1034,7 +1163,7 @@ static struct gattc_sub *gattc_sub_find(struct bt_conn *conn)
if (conn == NULL) {
if (bt_addr_le_eq(&sub->peer, BT_ADDR_LE_ANY)) {
LOG_DBG("SubNew[%u]", i);
LOG_DBG("GattcSubFindNew[%u]", i);
return sub;
}
} else if (bt_conn_is_peer_addr_le(conn, sub->id, &sub->peer)) {
@@ -1046,7 +1175,7 @@ static struct gattc_sub *gattc_sub_find(struct bt_conn *conn)
if (conn) {
/* LOG_DBG("SubNotFound"); */
} else {
LOG_WRN("NoFreeSub");
LOG_WRN("GattcSubFindNoFree");
}
return NULL;
@@ -1064,7 +1193,7 @@ static struct gattc_sub *gattc_sub_add(struct bt_conn *conn)
if (sub) {
bt_addr_le_copy(&sub->peer, &conn->le.dst);
sub->id = conn->id;
LOG_DBG("SubPeer[%s]", bt_addr_le_str(&sub->peer));
LOG_DBG("GattcSubAddPeer[%s]", bt_addr_le_str(&sub->peer));
}
}
@@ -1098,6 +1227,10 @@ static void gattc_sub_clear(struct bt_conn *conn)
tmp->value = 0;
}
sys_slist_init(&sub->list);
/* Release the pool slot back to free. Without this it stays bound to the disconnected
* peer's address, so a later connection from a different peer finds no free slot -> -ENOMEM. */
bt_addr_le_copy(&sub->peer, BT_ADDR_LE_ANY);
}
_IDF_ONLY
@@ -1126,7 +1259,7 @@ static uint8_t gattc_ccc_discover_cb(struct bt_conn *conn,
if (params->type == BT_GATT_DISCOVER_DESCRIPTOR) {
memset(params, 0, sizeof(*params));
LOG_DBG("CccHdl[%u]", attr->handle);
LOG_DBG("GattcCccDiscCbHdl[%u]", attr->handle);
sub_params->ccc_handle = attr->handle;
@@ -1193,7 +1326,7 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
* can resubscribe.
*/
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("GattSubNotConn[%u][%u]", conn->handle, conn->state);
LOG_ERR("GattcSubNotConn[%u][%u]", conn->handle, conn->state);
params->value_handle = 0; /* unlinked: clear retry guard */
return -ENOTCONN;
}
@@ -1219,7 +1352,7 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
*/
if (tmp->value_handle == params->value_handle &&
tmp->value >= params->value) {
LOG_INF("AnotherSubExist[%04x]", tmp->value);
LOG_INF("GattcSubAnotherExist[%04x]", tmp->value);
has_subscription = true;
}
}
@@ -1232,7 +1365,7 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
if (params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE) {
int err = gattc_ccc_discover(conn, params);
if (err) {
LOG_ERR("DiscCccFail[%d]", err);
LOG_ERR("GattcSubDiscCccFail[%d]", err);
params->value_handle = 0; /* unlinked: clear retry guard */
return err;
}
@@ -1277,7 +1410,7 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
params->value, params->value_handle, params->ccc_handle, params->end_handle);
if (conn->state != BT_CONN_CONNECTED) {
LOG_ERR("GattUnsubNotConn[%u][%u]", conn->handle, conn->state);
LOG_ERR("GattcUnsubNotConn[%u][%u]", conn->handle, conn->state);
return -ENOTCONN;
}
@@ -1298,7 +1431,7 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
* characteristic value handle.
*/
if (tmp->value_handle == params->value_handle) {
LOG_INF("AnotherSubFound");
LOG_INF("GattcUnsubAnotherSub");
has_subscription = true;
}
}
@@ -1321,7 +1454,7 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
* the write was issued. Not a real failure.
*/
if (err != -ENOTCONN) {
LOG_ERR("WrCccFail[%d]", err);
LOG_ERR("GattcUnsubWrCccFail[%d]", err);
}
return err;
}
@@ -1330,7 +1463,7 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
sys_slist_find_and_remove(&sub->list, &params->node);
if (sys_slist_is_empty(&sub->list)) {
LOG_DBG("SubListEmpty");
LOG_DBG("GattcUnsubListEmpty");
bt_addr_le_copy(&sub->peer, BT_ADDR_LE_ANY);
}
@@ -1358,7 +1491,7 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
}
if (params->handle_count > 1) {
LOG_ERR("NotSuppToRdHdl[%u]", params->handle_count);
LOG_ERR("GattRdNotSuppHdl[%u]", params->handle_count);
return -ENOTSUP;
}
@@ -1366,7 +1499,7 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
assert(params->by_uuid.uuid);
if (params->by_uuid.uuid->type != BT_UUID_TYPE_16) {
LOG_ERR("InvUuid[%s]", bt_uuid_str(params->by_uuid.uuid));
LOG_ERR("GattRdInvUuid[%s]", bt_uuid_str(params->by_uuid.uuid));
return -ENOTSUP;
}
}
@@ -1395,7 +1528,7 @@ int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
}
if (params->offset != 0) {
LOG_ERR("PrepWrNotSupp[%u][%u]", params->handle, params->offset);
LOG_ERR("GattWrPrepNotSupp[%u][%u]", params->handle, params->offset);
return -ENOTSUP;
}
@@ -1427,7 +1560,7 @@ int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle,
}
if (sign) {
LOG_ERR("WrCmdNotSupp");
LOG_ERR("GattWrCmdNotSupp");
return -ENOTSUP;
}
@@ -1472,9 +1605,28 @@ void bt_le_acl_conn_disconnected_gatt_listener(uint16_t conn_handle)
conn = bt_le_acl_conn_find(conn_handle);
if (conn) {
gattc_sub_clear(conn);
/* Bonded peers keep their cfg (reconnect notifications resume without a
* re-subscribe); free non-bonded peers' cfg so the pool isn't leaked. */
if (!bt_le_bond_exists(conn->id, &conn->le.dst)) {
gatts_clear_ccc_cfg_by_conn(conn);
}
}
}
_LIB_ONLY
void bt_le_acl_conn_bond_deleted_gatt_listener(uint8_t id, const bt_addr_le_t *peer)
{
ARG_UNUSED(id);
/* Cfg is retained across disconnect only for bonded peers (see disconnect
* listener), so bond deletion must drop it — else an unbonded reconnect from
* that address gets notified for a subscription it never made. */
LOG_DBG("AclConnBondDeletedGattListener");
gatts_clear_ccc_cfg_by_addr(peer);
}
void bt_le_gatt_handle_event(uint8_t *data, size_t data_len)
{
assert(data && data_len);
@@ -17,7 +17,8 @@ extern "C" {
struct bt_le_ext_adv *bt_le_ext_adv_find(uint8_t adv_handle);
int bt_le_ext_adv_new_safe(uint8_t adv_handle);
int bt_le_ext_adv_new_safe(uint8_t adv_handle, uint8_t addr_type,
const uint8_t *addr, uint8_t sid);
int bt_le_ext_adv_delete_safe(uint8_t adv_handle);
@@ -94,6 +94,10 @@ struct bt_le_gap_app_security_change_param {
uint8_t sec_level;
uint8_t bonded : 1;
struct bt_le_addr dst;
/* Bonded LTK (CSIS SIRK-encryption key K), captured by the adapter; the
* safe handler hands it to bt_conn_le_set_ltk. Valid only if ltk_present. */
uint8_t ltk[16];
uint8_t ltk_present : 1;
};
struct bt_le_gap_app_identity_resolve_param {
@@ -41,6 +41,8 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
int bt_le_acl_conn_delete(uint16_t conn_handle);
void bt_conn_le_set_ltk(struct bt_conn *conn, const uint8_t *ltk);
int bt_le_acl_conn_update(uint16_t conn_handle,
uint8_t sec_level,
bool *encrypted);
@@ -50,6 +50,7 @@ struct bt_le_gattc_discover_event {
struct bt_le_gattc_disc_cmpl_event {
uint8_t status;
uint16_t conn_handle;
uint16_t conn_id;
};
struct bt_le_gatts_subscribe_event {
@@ -65,6 +66,7 @@ struct bt_le_gatts_subscribe_event {
struct bt_le_gattc_notify_rx_event {
bool is_notify;
uint16_t conn_handle;
uint16_t conn_id;
uint16_t attr_handle;
uint16_t len;
uint8_t *value;
@@ -73,6 +75,7 @@ struct bt_le_gattc_notify_rx_event {
struct bt_le_gatts_notify_tx_event {
bool is_notify;
uint16_t conn_handle;
uint16_t conn_id;
uint16_t attr_handle;
uint8_t status;
};
@@ -90,29 +93,34 @@ struct bt_le_addr_simple {
struct bt_le_gattc_connect_event {
uint16_t conn_handle;
uint16_t conn_id;
uint8_t role;
struct bt_le_addr_simple peer;
};
struct bt_le_gattc_disconnect_event {
uint16_t conn_handle;
uint16_t conn_id;
uint8_t reason;
};
struct bt_le_gattc_open_event {
uint8_t status;
uint16_t conn_handle;
uint16_t conn_id;
};
struct bt_le_gattc_mtu_event {
uint8_t status;
uint16_t conn_handle;
uint16_t conn_id;
uint16_t mtu;
};
struct bt_le_gattc_read_chrc_event {
uint8_t status;
uint16_t conn_handle;
uint16_t conn_id;
uint16_t attr_handle;
uint16_t len;
uint8_t *value;
@@ -121,28 +129,33 @@ struct bt_le_gattc_read_chrc_event {
struct bt_le_gattc_write_chrc_event {
uint8_t status;
uint16_t conn_handle;
uint16_t conn_id;
uint16_t attr_handle;
uint16_t offset;
};
struct bt_le_gatts_connect_event {
uint16_t conn_handle;
uint16_t conn_id;
uint8_t role;
struct bt_le_addr_simple peer;
};
struct bt_le_gatts_disconnect_event {
uint16_t conn_handle;
uint16_t conn_id;
uint8_t reason;
};
struct bt_le_gatts_mtu_event {
uint16_t conn_handle;
uint16_t conn_id;
uint16_t mtu;
};
struct bt_le_gatts_read_event {
uint16_t conn_handle;
uint16_t conn_id;
uint32_t trans_id;
uint8_t peer[6];
uint16_t attr_handle;
@@ -153,6 +166,7 @@ struct bt_le_gatts_read_event {
struct bt_le_gatts_write_event {
uint16_t conn_handle;
uint16_t conn_id;
uint32_t trans_id;
uint8_t peer[6];
uint16_t attr_handle;
@@ -163,6 +177,13 @@ struct bt_le_gatts_write_event {
uint8_t *value;
};
struct bt_le_gatts_exec_write_event {
uint16_t conn_handle;
uint16_t conn_id;
uint32_t trans_id;
bool exec; /* true = execute the queued long write, false = cancel */
};
struct bt_le_gatt_event_param {
uint8_t type;
@@ -186,6 +207,7 @@ struct bt_le_gatt_event_param {
struct bt_le_gatts_mtu_event gatts_mtu;
struct bt_le_gatts_read_event gatts_read;
struct bt_le_gatts_write_event gatts_write;
struct bt_le_gatts_exec_write_event gatts_exec_write;
};
};
@@ -209,6 +231,7 @@ enum {
BT_LE_GATTS_MTU_EVENT,
BT_LE_GATTS_READ_EVENT,
BT_LE_GATTS_WRITE_EVENT,
BT_LE_GATTS_EXEC_WRITE_EVENT,
BT_LE_GATT_EVENT_MAX,
};
@@ -253,7 +276,7 @@ struct notify_data {
struct bt_gatt_attr *bt_gatts_find_attr_by_handle(uint16_t handle);
bool bt_gatts_find_attr_by_uuid(struct notify_data *found,
const struct bt_uuid *uuid);
const struct bt_uuid *uuid);
int bt_gatts_sub_changed(uint16_t conn_handle,
uint16_t ccc_handle,
@@ -267,6 +290,8 @@ struct gattc_sub *bt_gattc_sub_find(struct bt_conn *conn);
void bt_le_acl_conn_disconnected_gatt_listener(uint16_t conn_handle);
void bt_le_acl_conn_bond_deleted_gatt_listener(uint8_t id, const bt_addr_le_t *peer);
void bt_le_gatt_handle_event(uint8_t *data, size_t data_len);
#ifdef __cplusplus
+17 -12
View File
@@ -91,14 +91,14 @@ int bt_le_iso_cb_register_safe(struct bt_le_iso_cb *cb)
LOG_DBG("IsoCbReg");
if (cb == NULL) {
LOG_ERR("IsoCbNull");
LOG_ERR("IsoCbRegNull");
return -EINVAL;
}
bt_le_host_lock();
if (sys_slist_find(&iso_cbs, &cb->node, NULL)) {
LOG_WRN("IsoCbExist");
LOG_WRN("IsoCbRegExist");
err = -EEXIST;
goto end;
}
@@ -117,7 +117,7 @@ void bt_le_iso_cb_unregister_safe(struct bt_le_iso_cb *cb)
LOG_DBG("IsoCbUnreg");
if (cb == NULL) {
LOG_ERR("IsoCbNull");
LOG_ERR("IsoCbUnregNull");
return;
}
@@ -415,7 +415,7 @@ static int iso_tx_sdu_insert(struct bt_iso_chan *chan, const uint8_t *sdu, uint1
sdu_node = calloc(1, sizeof(*sdu_node));
if (sdu_node == NULL) {
LOG_ERR("IsoTxInsertNoMem[%u]", sizeof(*sdu_node));
LOG_ERR("IsoTxSduInsertNoMem[%u]", sizeof(*sdu_node));
return -ENOMEM;
}
@@ -445,7 +445,7 @@ static void iso_tx_sdu_clear(uint16_t handle, bool is_big)
if ((is_big == false && iso->handle == handle) ||
(is_big && iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER &&
iso->iso.info.broadcaster.big_handle == handle)) {
LOG_DBG("IsoSduRem[%u]", sdu_node->sdu_len);
LOG_DBG("IsoTxSduClearRem[%u]", sdu_node->sdu_len);
sys_slist_remove(&iso_tx_sdu_list, prev, &sdu_node->node);
@@ -463,22 +463,27 @@ static int validate_iso_send(struct bt_iso_chan *chan, struct net_buf *buf)
{
if (chan == NULL || buf == NULL ||
(buf->data == NULL && buf->len != 0)) {
LOG_ERR("InvSendParam[%p][%p]", chan, buf);
LOG_ERR("IsoSendInvParam[%p][%p]", chan, buf);
return -EINVAL;
}
if (chan->iso == NULL) {
LOG_ERR("IsoSendChanNoIso");
return -ENOTCONN;
}
if (chan->state != BT_ISO_STATE_CONNECTED) {
LOG_ERR("ChanNotConn[%u]", chan->iso->handle);
LOG_ERR("IsoSendChanNotConn[%u]", chan->iso->handle);
return -ENOTCONN;
}
if (chan->iso->iso.info.can_send == false) {
LOG_ERR("ChanNotSend[%u]", chan->iso->handle);
LOG_ERR("IsoSendChanNotSend[%u]", chan->iso->handle);
return -EINVAL;
}
if (buf->len > chan->qos->tx->sdu) {
LOG_ERR("ChanCannotSend[%u][%u][%u]",
LOG_ERR("IsoSendChanCannotSend[%u][%u][%u]",
chan->iso->handle, buf->len, chan->qos->tx->sdu);
return -EMSGSIZE;
}
@@ -510,7 +515,7 @@ static int iso_tx_now(struct bt_iso_chan *chan, const uint8_t *sdu,
pkt = malloc(4 + data_total_len);
if (pkt == NULL) {
LOG_ERR("IsoTxNoMem[%u]", 4 + data_total_len);
LOG_ERR("IsoTxNowNoMem[%u]", 4 + data_total_len);
return -ENOMEM;
}
@@ -563,7 +568,7 @@ static int iso_tx_now(struct bt_iso_chan *chan, const uint8_t *sdu,
* If using controller from other vendors, the pkt buffer may
* needs to be freed here.
*/
LOG_WRN("IsoTxFail[%d]", err);
LOG_WRN("IsoTxNowFail[%d]", err);
return err;
}
@@ -642,7 +647,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
* If using controller from other vendors, the pkt buffer may
* needs to be freed here.
*/
LOG_WRN("IsoTxFail[%d]", err);
LOG_WRN("IsoTxCompFail[%d]", err);
}
free(sdu_node);
+11 -3
View File
@@ -12,6 +12,7 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/hci_types.h>
@@ -199,7 +200,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad
assert(adv_addr);
LOG_DBG("PaSyncLookupAddr[%s][%u]", bt_addr_le_str(adv_addr), sid);
LOG_INF("PaSyncLookupAddr[%s][%u]", bt_addr_le_str(adv_addr), sid);
for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) {
if (atomic_test_bit(per_adv_sync_pool[i].flags, BT_PER_ADV_SYNC_SYNCED) &&
@@ -275,7 +276,7 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
{
struct bt_le_per_adv_sync *per_adv_sync;
LOG_DBG("PaSyncNew[%u][%u][%u][%u][%u]",
LOG_INF("PaSyncNew[%u][%u][%u][%u][%u]",
sync_handle, sid, phy, interval, conn_handle);
if (addr_type > BT_ADDR_LE_RANDOM_ID || addr == NULL) {
@@ -301,9 +302,16 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
per_adv_sync->interval = interval;
per_adv_sync->conn_handle = conn_handle;
per_adv_sync->addr.type = addr_type;
#if CONFIG_BT_BLUEDROID_ENABLED
/* Bluedroid delivers the advertiser address MSB-first, but the lib compares in
* on-air/LSB-first order (bt_addr_le_t.a.val). NimBLE already supplies on-air
* order, so reverse only here. */
sys_memcpy_swap(per_adv_sync->addr.a.val, addr, BT_ADDR_SIZE);
#else
memcpy(per_adv_sync->addr.a.val, addr, BT_ADDR_SIZE);
#endif
LOG_DBG("Addr[%s]", bt_addr_le_str(&per_adv_sync->addr));
LOG_INF("PaSyncAddr[%s]", bt_addr_le_str(&per_adv_sync->addr));
if (out_sync) {
*out_sync = per_adv_sync;
+10 -1
View File
@@ -2881,7 +2881,16 @@ void hci_le_big_terminate(struct net_buf *buf)
LOG_DBG("BigTerminated[%u][%p]", big->handle, big);
big_disconnect(big, evt->reason);
/* Source BIG is only terminated by the local host → reason must be
* LOCALHOST_TERM (Core 7.7.65.28); fix up controllers reporting otherwise. */
uint8_t reason = evt->reason;
if (reason != BT_HCI_ERR_LOCALHOST_TERM_CONN) {
LOG_INF("BigTermReasonFixup[%02x][%02x]", reason, BT_HCI_ERR_LOCALHOST_TERM_CONN);
reason = BT_HCI_ERR_LOCALHOST_TERM_CONN;
}
big_disconnect(big, reason);
}
#endif /* CONFIG_BT_ISO_BROADCASTER */
+12 -4
View File
@@ -251,14 +251,14 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms)
return 0;
}
int k_work_schedule_periodic(struct k_work_delayable *dwork, k_timeout_t period_ms)
int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_us)
{
int err;
LOG_DBG("WorkSchedulePeriodic[%p][%u]", dwork, period_ms);
LOG_DBG("WorkSchedulePeriodicUs[%p][%llu]", dwork, (unsigned long long)period_us);
assert(dwork);
assert(period_ms > 0);
assert(period_us > 0);
if (dwork->work.timer == NULL) {
LOG_WRN("TimerNotCreated");
@@ -267,7 +267,7 @@ int k_work_schedule_periodic(struct k_work_delayable *dwork, k_timeout_t period_
esp_timer_stop(dwork->work.timer);
err = esp_timer_start_periodic(dwork->work.timer, (uint64_t)period_ms * 1000);
err = esp_timer_start_periodic(dwork->work.timer, period_us);
if (err) {
LOG_ERR("StartPeriodicTimerFail[%d]", err);
return -EIO;
@@ -276,6 +276,14 @@ int k_work_schedule_periodic(struct k_work_delayable *dwork, k_timeout_t period_
return 0;
}
int k_work_schedule_periodic(struct k_work_delayable *dwork, k_timeout_t period_ms)
{
/* Sub-millisecond ISO SDU intervals (e.g. 7500, 8163, 10884 us) lose
* precision when forced to whole milliseconds; callers that need the exact
* interval should use k_work_schedule_periodic_us(). */
return k_work_schedule_periodic_us(dwork, (uint64_t)period_ms * 1000);
}
k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork)
{
k_timeout_t timeout;
+141 -15
View File
@@ -7,16 +7,27 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zephyr/net_buf.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/conn.h>
#include <../host/keys.h>
#include <../host/hci_core.h>
#if CONFIG_BT_BLUEDROID_ENABLED
#include "esp_gap_ble_api.h"
#else
#include "host/ble_gap.h"
#include "host/ble_store.h"
#endif
LOG_MODULE_REGISTER(ISO_UTILS, CONFIG_BT_ISO_LOG_LEVEL);
struct bt_dev bt_dev;
uint8_t bt_get_phy(uint8_t hci_phy)
@@ -29,44 +40,159 @@ uint8_t bt_get_phy(uint8_t hci_phy)
case BT_HCI_LE_PHY_CODED:
return BT_GAP_LE_PHY_CODED;
default:
LOG_WRN("UnknownHciPhy[%u]", hci_phy);
return 0;
}
}
static struct bt_keys key_pool[CONFIG_BT_MAX_PAIRED];
/* Query the active host's persistent bond store (the old local key_pool was never
* populated, so bt_le_bond_exists() was always false). `id` ignored (single identity);
* addresses stay in native host byte order. */
#if CONFIG_BT_BLUEDROID_ENABLED
void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info,
void *user_data),
void *user_data)
{
int num = esp_ble_get_bond_device_num();
esp_ble_bond_dev_t *list;
assert(func);
(void)id;
for (size_t i = 0; i < ARRAY_SIZE(key_pool); i++) {
struct bt_keys *keys = &key_pool[i];
LOG_DBG("[B]ForeachBond[%d]", num);
if (keys->keys && keys->id == id) {
struct bt_bond_info info;
if (num <= 0) {
return;
}
bt_addr_le_copy(&info.addr, &keys->addr);
list = calloc(num, sizeof(*list));
if (list == NULL) {
LOG_ERR("[B]BondAllocFail[%d]", num);
return;
}
if (esp_ble_get_bond_device_list(&num, list) == ESP_OK) {
for (int i = 0; i < num; i++) {
struct bt_bond_info info = {0};
info.addr.type = list[i].bd_addr_type;
memcpy(info.addr.a.val, list[i].bd_addr, BT_ADDR_SIZE);
func(&info, user_data);
}
} else {
LOG_WRN("[B]BondReadFail");
}
free(list);
}
bool bt_le_bond_exists(uint8_t id, const bt_addr_le_t *addr)
{
int num = esp_ble_get_bond_device_num();
esp_ble_bond_dev_t *list;
bool found = false;
(void)id;
if (addr == NULL) {
LOG_WRN("[B]BondNullAddr");
return false;
}
if (num == 0) {
LOG_DBG("[B]BondNone"); /* genuinely no bonds stored */
return false;
}
/* A query failure (num<0, OOM, read error) doesn't mean unbonded — the record is
* intact, we just can't read it. Callers treat false as "not bonded" and drop the
* peer's CCC/keys, so fail safe to true rather than destroy a real bond's state. */
if (num < 0) {
LOG_WRN("[B]BondNumFail[%d]", num);
return true;
}
list = calloc(num, sizeof(*list));
if (list == NULL) {
LOG_ERR("[B]BondAllocFail[%d]", num);
return true;
}
if (esp_ble_get_bond_device_list(&num, list) != ESP_OK) {
LOG_WRN("[B]BondReadFail");
free(list);
return true;
}
for (int i = 0; i < num; i++) {
/* Match the 6-byte address only: bd_addr uniquely identifies a bond,
* and the type encodings (public/random vs *_ID/RPA) do not map 1:1
* across the host boundary. */
if (memcmp(list[i].bd_addr, addr->a.val, BT_ADDR_SIZE) == 0) {
found = true;
break;
}
}
LOG_INF("[B]BondExists[%d][%02x:%02x:%02x:%02x:%02x:%02x]", found,
addr->a.val[0], addr->a.val[1], addr->a.val[2],
addr->a.val[3], addr->a.val[4], addr->a.val[5]);
free(list);
return found;
}
#else /* NimBLE host */
void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info,
void *user_data),
void *user_data)
{
ble_addr_t peers[CONFIG_BT_MAX_PAIRED];
int num = 0;
assert(func);
(void)id;
if (ble_store_util_bonded_peers(peers, &num, ARRAY_SIZE(peers)) != 0) {
LOG_WRN("[N]BondReadFail");
return;
}
LOG_DBG("[N]ForeachBond[%d]", num);
for (int i = 0; i < num; i++) {
struct bt_bond_info info = {0};
info.addr.type = peers[i].type;
memcpy(info.addr.a.val, peers[i].val, BT_ADDR_SIZE);
func(&info, user_data);
}
}
bool bt_le_bond_exists(uint8_t id, const bt_addr_le_t *addr)
{
struct ble_store_value_sec value = {0};
struct ble_store_key_sec key = {0};
(void)id;
if (addr == NULL) {
LOG_WRN("[N]BondNullAddr");
return false;
}
for (size_t i = 0; i < ARRAY_SIZE(key_pool); i++) {
struct bt_keys *keys = &key_pool[i];
key.peer_addr.type = addr->type;
memcpy(key.peer_addr.val, addr->a.val, BT_ADDR_SIZE);
if (keys->keys && keys->id == id &&
bt_addr_le_eq(&keys->addr, addr)) {
return true;
}
}
bool found = ble_store_read_peer_sec(&key, &value) == 0;
return false;
LOG_INF("[N]BondExists[%d][%02x:%02x:%02x:%02x:%02x:%02x]", found,
addr->a.val[0], addr->a.val[1], addr->a.val[2],
addr->a.val[3], addr->a.val[4], addr->a.val[5]);
return found;
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
@@ -9,6 +9,8 @@
#pragma once
#include <zephyr/bluetooth/addr.h>
/* bt_dev flags: the flags defined here represent BT controller state */
enum {
/** The application either explicitly or implicitly instructed the stack to scan
@@ -46,6 +48,12 @@ struct bt_le_ext_adv {
/* Advertising handle */
uint8_t handle;
/* Advertising address */
bt_addr_le_t addr;
/* Advertising SID */
uint8_t sid;
ATOMIC_DEFINE(flags, BT_ADV_NUM_FLAGS);
};
@@ -893,7 +893,10 @@ ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn,
})), \
BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data)
#define BT_GATT_CCC_MAX (1)
/* Depth of each CCC's subscriber-config pool: one slot per bonded peer (subscription
* retained across disconnect for reconnect notify) + the active connection. A 1-deep
* pool made a second bonded subscriber fail with CccNoFreeCfg. */
#define BT_GATT_CCC_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN)
/** @brief GATT CCC configuration entry.
*
@@ -916,7 +919,8 @@ struct bt_gatt_ccc_cfg {
/** Internal representation of CCC value */
struct bt_gatt_ccc_managed_user_data {
/** Configuration for each connection */
struct bt_gatt_ccc_cfg cfg[BT_GATT_CCC_MAX];
struct bt_gatt_ccc_cfg *cfg;
uint8_t cfg_count;
/** Highest value of all connected peer's subscriptions */
uint16_t value;
@@ -1011,13 +1015,12 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
* @param _match Configuration match callback.
*/
#define BT_GATT_CCC_MANAGED_USER_DATA_INIT(_changed, _write, _match) \
{ \
.cfg = { \
[0 ... (BT_GATT_CCC_MAX - 1)] = {0}, \
}, \
.cfg_changed = _changed, \
.cfg_write = _write, \
.cfg_match = _match, \
{ \
.cfg = NULL, \
.cfg_count = 0, \
.cfg_changed = _changed, \
.cfg_write = _write, \
.cfg_match = _match, \
}
/**
@@ -4153,6 +4153,15 @@ struct bt_uuid_128 {
*/
#define BT_UUID_CSIS_RANK \
BT_UUID_DECLARE_16(BT_UUID_CSIS_RANK_VAL)
/**
* @brief Coordinated Set Name value (CSIS v1.1)
*/
#define BT_UUID_CSIS_SET_NAME_VAL 0x2c1a
/**
* @brief Coordinated Set Name
*/
#define BT_UUID_CSIS_SET_NAME \
BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_NAME_VAL)
/**
* @brief GATT Characteristic Encrypted Data Key Material UUID Value
*/
@@ -259,6 +259,8 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t delay);
int k_work_schedule_periodic(struct k_work_delayable *dwork, k_timeout_t period_ms);
int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_us);
k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork);
/* Slist */