From de84e7398ba2ddacbd186d3f2a58fd2727fb82cc Mon Sep 17 00:00:00 2001 From: Liu Linyan Date: Mon, 29 Jun 2026 20:14:37 +0800 Subject: [PATCH] feat(ble_audio): Miscellaneous update for verifying with PTS cases --- components/bt/esp_ble_audio/CMakeLists.txt | 6 + .../bt/esp_ble_audio/Kconfig/Kconfig.csip.in | 6 + .../bt/esp_ble_audio/Kconfig/Kconfig.mcs.in | 10 + .../esp_ble_audio/api/esp_ble_audio_bap_api.c | 21 +- .../esp_ble_audio/api/esp_ble_audio_cap_api.c | 22 +- .../api/esp_ble_audio_csip_api.c | 18 + .../esp_ble_audio/api/esp_ble_audio_has_api.c | 26 +- .../api/esp_ble_audio_media_proxy_api.c | 34 ++ .../esp_ble_audio/api/esp_ble_audio_tbs_api.c | 51 +- .../esp_ble_audio/api/esp_ble_audio_vcp_api.c | 16 + .../api/include/esp_ble_audio_bap_api.h | 27 + .../esp_ble_audio_bap_lc3_preset_defs.h | 176 +++--- .../api/include/esp_ble_audio_cap_api.h | 4 +- .../api/include/esp_ble_audio_csip_api.h | 22 +- .../api/include/esp_ble_audio_defs.h | 2 + .../api/include/esp_ble_audio_has_api.h | 14 + .../include/esp_ble_audio_media_proxy_api.h | 18 + .../api/include/esp_ble_audio_tbs_api.h | 41 ++ .../api/include/esp_ble_audio_vcp_api.h | 23 + .../bluedroid/include/bluedroid/init.h | 1 + .../include/bluedroid/profiles/gmas.h | 25 + .../include/bluedroid/profiles/mcs.h | 4 + .../include/bluedroid/profiles/tbs.h | 4 + .../bluedroid/include/bluedroid/server.h | 3 + .../host/adapter/bluedroid/init.c | 53 +- .../host/adapter/bluedroid/profiles/gmas.c | 56 ++ .../host/adapter/bluedroid/profiles/mcs.c | 138 ++++- .../host/adapter/bluedroid/profiles/tbs.c | 55 ++ .../host/adapter/bluedroid/server.c | 49 +- .../host/adapter/nimble/include/nimble/init.h | 1 + .../nimble/include/nimble/profiles/gmas.h | 25 + .../nimble/include/nimble/profiles/mcs.h | 4 + .../nimble/include/nimble/profiles/tbs.h | 4 + .../esp_ble_audio/host/adapter/nimble/init.c | 64 ++- .../host/adapter/nimble/profiles/csis.c | 25 +- .../host/adapter/nimble/profiles/gmas.c | 203 +++++++ .../host/adapter/nimble/profiles/mcs.c | 140 ++++- .../host/adapter/nimble/profiles/tbs.c | 112 ++++ .../host/common/include/common/init.h | 8 +- .../bt/esp_ble_audio/host/common/init.c | 72 ++- .../host/services/ots/ots_l2cap.c | 5 + .../include/zephyr/bluetooth/audio/bap.h | 19 + .../include/zephyr/bluetooth/audio/cap.h | 7 + .../include/zephyr/bluetooth/audio/csip.h | 34 +- .../include/zephyr/bluetooth/audio/has.h | 14 + .../zephyr/bluetooth/audio/media_proxy.h | 18 + .../include/zephyr/bluetooth/audio/tbs.h | 45 ++ .../include/zephyr/bluetooth/audio/vcp.h | 40 ++ .../bt/esp_ble_audio/lib/include/audio.h | 15 +- components/bt/esp_ble_audio/lib/lib | 2 +- components/bt/esp_ble_iso/Kconfig.in | 4 +- .../esp_ble_iso/api/esp_ble_iso_common_api.c | 7 +- .../api/include/esp_ble_iso_common_api.h | 3 + .../esp_ble_iso/host/adapter/bluedroid/gap.c | 53 +- .../host/adapter/bluedroid/gatt/gatt.c | 526 ++++++++++++++---- .../bluedroid/include/bluedroid/gatt.h | 17 + .../esp_ble_iso/host/adapter/bluedroid/iso.c | 14 +- .../bt/esp_ble_iso/host/adapter/nimble/gap.c | 21 + .../host/adapter/nimble/gatt/gatt.nrp.c | 32 +- .../esp_ble_iso/host/adapter/nimble/l2cap.c | 66 +-- components/bt/esp_ble_iso/host/common/adv.c | 9 +- .../bt/esp_ble_iso/host/common/app/gap.c | 11 + components/bt/esp_ble_iso/host/common/conn.c | 40 ++ components/bt/esp_ble_iso/host/common/gatt.c | 278 ++++++--- .../host/common/include/common/adv.h | 3 +- .../host/common/include/common/app/gap.h | 4 + .../host/common/include/common/conn.h | 2 + .../host/common/include/common/gatt.h | 27 +- components/bt/esp_ble_iso/host/common/iso.c | 29 +- components/bt/esp_ble_iso/host/common/scan.c | 14 +- components/bt/esp_ble_iso/host/iso/iso.c | 11 +- components/bt/esp_ble_iso/host/utils/timer.c | 16 +- components/bt/esp_ble_iso/host/utils/utils.c | 156 +++++- .../include/subsys/bluetooth/host/hci_core.h | 8 + .../include/zephyr/bluetooth/gatt.h | 21 +- .../include/zephyr/bluetooth/uuid.h | 9 + .../bt/esp_ble_iso/include/zephyr/kernel.h | 2 + .../example_utils/ble_audio_example_utils.c | 2 +- .../tmap/bmr/main/bap_broadcast_sink.c | 4 +- .../example_utils/ble_iso_example_utils.c | 2 +- 80 files changed, 2751 insertions(+), 422 deletions(-) create mode 100644 components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/gmas.h create mode 100644 components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/gmas.c create mode 100644 components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/gmas.h create mode 100644 components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c diff --git a/components/bt/esp_ble_audio/CMakeLists.txt b/components/bt/esp_ble_audio/CMakeLists.txt index f710811f627..fcc052ee98d 100644 --- a/components/bt/esp_ble_audio/CMakeLists.txt +++ b/components/bt/esp_ble_audio/CMakeLists.txt @@ -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" diff --git a/components/bt/esp_ble_audio/Kconfig/Kconfig.csip.in b/components/bt/esp_ble_audio/Kconfig/Kconfig.csip.in index c2dda10cc0d..a754583e5e2 100644 --- a/components/bt/esp_ble_audio/Kconfig/Kconfig.csip.in +++ b/components/bt/esp_ble_audio/Kconfig/Kconfig.csip.in @@ -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 #################### diff --git a/components/bt/esp_ble_audio/Kconfig/Kconfig.mcs.in b/components/bt/esp_ble_audio/Kconfig/Kconfig.mcs.in index 001a6552e80..b755b30de45 100644 --- a/components/bt/esp_ble_audio/Kconfig/Kconfig.mcs.in +++ b/components/bt/esp_ble_audio/Kconfig/Kconfig.mcs.in @@ -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 diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c index 1d41fa6fa31..d7a401a61b7 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_bap_api.c @@ -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 diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c index 25f60ee7dc6..a6959bac8da 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_cap_api.c @@ -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( diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c index 54b07c9db2a..a0de9a5f417 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_csip_api.c @@ -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) { diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c index 0e2467df333..7bb847c555b 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_has_api.c @@ -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; diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c index 5f1754b6ae3..6169851617e 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_media_proxy_api.c @@ -5,6 +5,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include + #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 diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c index d9dddbfa06c..8169c4bb679 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_tbs_api.c @@ -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; } diff --git a/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c b/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c index 9d606766e31..5e90d3bce7e 100644 --- a/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c +++ b/components/bt/esp_ble_audio/api/esp_ble_audio_vcp_api.c @@ -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 diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_api.h index 394dec325f1..2a8cc51a6c4 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_api.h @@ -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. * diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_lc3_preset_defs.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_lc3_preset_defs.h index ee9446b497d..aa3d1446efe 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_lc3_preset_defs.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_bap_lc3_preset_defs.h @@ -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. diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_cap_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_cap_api.h index 738c4dcabb4..5eae1ab3bdd 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_cap_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_cap_api.h @@ -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. */ diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_csip_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_csip_api.h index 6dbe03e49b7..a31062f0a59 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_csip_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_csip_api.h @@ -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. * diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_defs.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_defs.h index df8e32fbd1a..1140ea732d8 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_defs.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_defs.h @@ -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 diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_has_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_has_api.h index 12e8bab46e0..930d4b46af3 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_has_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_has_api.h @@ -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. * diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_media_proxy_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_media_proxy_api.h index c8a9609670c..9802db0b296 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_media_proxy_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_media_proxy_api.h @@ -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. * diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_tbs_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_tbs_api.h index 97afcea3c2a..c11bab111c9 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_tbs_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_tbs_api.h @@ -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. * diff --git a/components/bt/esp_ble_audio/api/include/esp_ble_audio_vcp_api.h b/components/bt/esp_ble_audio/api/include/esp_ble_audio_vcp_api.h index 6d0c2168c9e..881396e1f92 100644 --- a/components/bt/esp_ble_audio/api/include/esp_ble_audio_vcp_api.h +++ b/components/bt/esp_ble_audio/api/include/esp_ble_audio_vcp_api.h @@ -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. * diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/init.h b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/init.h index c684349b846..4923388a935 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/init.h +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/init.h @@ -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" diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/gmas.h b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/gmas.h new file mode 100644 index 00000000000..666f8e32752 --- /dev/null +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/gmas.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 +#include + +#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_ */ diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/mcs.h b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/mcs.h index e4d89cc9ef2..f9d9b289c09 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/mcs.h +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/mcs.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 diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/tbs.h b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/tbs.h index 7e5e6e76962..b866089ccae 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/tbs.h +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/profiles/tbs.h @@ -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 diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/server.h b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/server.h index bf9e6c6e043..ab5b69aa73b 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/server.h +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/include/bluedroid/server.h @@ -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, }; diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/init.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/init.c index 424c9a9eafb..5383acb9abf 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/init.c +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/init.c @@ -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 diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/gmas.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/gmas.c new file mode 100644 index 00000000000..01531d818bf --- /dev/null +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/gmas.c @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#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); +} diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/mcs.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/mcs.c index a19c56ebe95..6869b474c04 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/mcs.c +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/mcs.c @@ -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; } diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/tbs.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/tbs.c index 6d0d2e70acb..2055bb2e066 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/tbs.c +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/profiles/tbs.c @@ -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; +} diff --git a/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c b/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c index 4870f3204d9..b7d306eb678 100644 --- a/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c +++ b/components/bt/esp_ble_audio/host/adapter/bluedroid/server.c @@ -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(); diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/init.h b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/init.h index 5e7d0211d3b..ce08a0b0db4 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/init.h +++ b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/init.h @@ -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" { diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/gmas.h b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/gmas.h new file mode 100644 index 00000000000..b38873f0899 --- /dev/null +++ b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/gmas.h @@ -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 +#include + +#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_ */ diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/mcs.h b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/mcs.h index f63593eb013..d6f0db35290 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/mcs.h +++ b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/mcs.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 diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/tbs.h b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/tbs.h index e41154028d0..9c14929e03b 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/tbs.h +++ b/components/bt/esp_ble_audio/host/adapter/nimble/include/nimble/profiles/tbs.h @@ -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 diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/init.c b/components/bt/esp_ble_audio/host/adapter/nimble/init.c index aac2efd5df8..ce338459170 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/init.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/init.c @@ -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); diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c index d2f0879436c..fa803b60c51 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/csis.c @@ -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); } diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c new file mode 100644 index 00000000000..525923fa7db --- /dev/null +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/gmas.c @@ -0,0 +1,203 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#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; +} diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c index ed04817e0b4..c5e1e351517 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/mcs.c @@ -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; +} diff --git a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c index 49551baefd3..4c67b780618 100644 --- a/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c +++ b/components/bt/esp_ble_audio/host/adapter/nimble/profiles/tbs.c @@ -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; +} diff --git a/components/bt/esp_ble_audio/host/common/include/common/init.h b/components/bt/esp_ble_audio/host/common/include/common/init.h index 153cbd8f465..480e0bb9e1c 100644 --- a/components/bt/esp_ble_audio/host/common/include/common/init.h +++ b/components/bt/esp_ble_audio/host/common/include/common/init.h @@ -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 } diff --git a/components/bt/esp_ble_audio/host/common/init.c b/components/bt/esp_ble_audio/host/common/init.c index b699adcaaa4..08505740f3c 100644 --- a/components/bt/esp_ble_audio/host/common/init.c +++ b/components/bt/esp_ble_audio/host/common/init.c @@ -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 } diff --git a/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c b/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c index 62552aae056..8e4dde8fa71 100644 --- a/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c +++ b/components/bt/esp_ble_audio/host/services/ots/ots_l2cap.c @@ -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); diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h index f05f4f47bd9..b78e45b180e 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/bap.h @@ -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 */ /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/cap.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/cap.h index 107780714ed..ca2be9b9c9e 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/cap.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/cap.h @@ -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); diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/csip.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/csip.h index 9acc6428527..ad533eed474 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/csip.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/csip.h @@ -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 */ diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h index 75212a28d7b..546ae5eebd4 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/has.h @@ -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 { /** diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h index b841788a240..0afa3453af5 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/media_proxy.h @@ -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 * diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h index f17e65b5de3..b0ffc14449c 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/tbs.h @@ -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. * diff --git a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h index d92b978cc78..db605ea47df 100644 --- a/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h +++ b/components/bt/esp_ble_audio/include/zephyr/bluetooth/audio/vcp.h @@ -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 * diff --git a/components/bt/esp_ble_audio/lib/include/audio.h b/components/bt/esp_ble_audio/lib/include/audio.h index 7f55c9443a0..37533c83d52 100644 --- a/components/bt/esp_ble_audio/lib/include/audio.h +++ b/components/bt/esp_ble_audio/lib/include/audio.h @@ -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); diff --git a/components/bt/esp_ble_audio/lib/lib b/components/bt/esp_ble_audio/lib/lib index 8af29109827..b69d90c8604 160000 --- a/components/bt/esp_ble_audio/lib/lib +++ b/components/bt/esp_ble_audio/lib/lib @@ -1 +1 @@ -Subproject commit 8af29109827eb9e3bc636b2a1ae6596fdfacaf4a +Subproject commit b69d90c86049f7d49bcc5ca7961c91e2691bc253 diff --git a/components/bt/esp_ble_iso/Kconfig.in b/components/bt/esp_ble_iso/Kconfig.in index 8de1b82ca46..ebb4f041719 100644 --- a/components/bt/esp_ble_iso/Kconfig.in +++ b/components/bt/esp_ble_iso/Kconfig.in @@ -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 diff --git a/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c b/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c index 5dcf4516f95..0f9f6deb998 100644 --- a/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c +++ b/components/bt/esp_ble_iso/api/esp_ble_iso_common_api.c @@ -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, <v[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; } diff --git a/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h b/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h index 3e0fdc0cdf7..a9560718f74 100644 --- a/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h +++ b/components/bt/esp_ble_iso/api/include/esp_ble_iso_common_api.h @@ -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; /** diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c b/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c index a000dca2652..ceb63f64dba 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/gap.c @@ -13,6 +13,7 @@ #include #include #include +#include #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; } diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c b/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c index 730c06f68fd..28aa82bb80a 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/gatt/gatt.c @@ -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(¶m->gatts_write); break; + case BT_LE_GATTS_EXEC_WRITE_EVENT: + handle_gatts_exec_write_event(¶m->gatts_exec_write); + break; + case BT_LE_GATTS_NOTIFY_TX_EVENT: handle_gatts_notify_tx_event(¶m->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 diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/gatt.h b/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/gatt.h index 7abfbcd7d37..a75d71a7026 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/gatt.h +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/include/bluedroid/gatt.h @@ -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); diff --git a/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c b/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c index 0b5450305b6..2716859fe33 100644 --- a/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c +++ b/components/bt/esp_ble_iso/host/adapter/bluedroid/iso.c @@ -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; diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/gap.c b/components/bt/esp_ble_iso/host/adapter/nimble/gap.c index c1770ebf8cb..5ca85a1dc6b 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/gap.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/gap.c @@ -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; diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c index b6679b8ea53..c40b1142ecd 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/gatt/gatt.nrp.c @@ -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; diff --git a/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c b/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c index f184dfcae69..53bedbc3506 100644 --- a/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c +++ b/components/bt/esp_ble_iso/host/adapter/nimble/l2cap.c @@ -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 */ diff --git a/components/bt/esp_ble_iso/host/common/adv.c b/components/bt/esp_ble_iso/host/common/adv.c index 666b0e8de9b..a359d95f575 100644 --- a/components/bt/esp_ble_iso/host/common/adv.c +++ b/components/bt/esp_ble_iso/host/common/adv.c @@ -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; diff --git a/components/bt/esp_ble_iso/host/common/app/gap.c b/components/bt/esp_ble_iso/host/common/app/gap.c index 75a07d3267c..ebf9aa79b8e 100644 --- a/components/bt/esp_ble_iso/host/common/app/gap.c +++ b/components/bt/esp_ble_iso/host/common/app/gap.c @@ -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); diff --git a/components/bt/esp_ble_iso/host/common/conn.c b/components/bt/esp_ble_iso/host/common/conn.c index cb0158b0c3e..c59ccba4244 100644 --- a/components/bt/esp_ble_iso/host/common/conn.c +++ b/components/bt/esp_ble_iso/host/common/conn.c @@ -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; } diff --git a/components/bt/esp_ble_iso/host/common/gatt.c b/components/bt/esp_ble_iso/host/common/gatt.c index 20b082ae2c7..ea5b1aabaa9 100644 --- a/components/bt/esp_ble_iso/host/common/gatt.c +++ b/components/bt/esp_ble_iso/host/common/gatt.c @@ -19,6 +19,7 @@ #include #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, ¶ms->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); diff --git a/components/bt/esp_ble_iso/host/common/include/common/adv.h b/components/bt/esp_ble_iso/host/common/include/common/adv.h index 229942139af..fdaf9d66ae7 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/adv.h +++ b/components/bt/esp_ble_iso/host/common/include/common/adv.h @@ -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); diff --git a/components/bt/esp_ble_iso/host/common/include/common/app/gap.h b/components/bt/esp_ble_iso/host/common/include/common/app/gap.h index fff7cf2e51d..b864d065f60 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/app/gap.h +++ b/components/bt/esp_ble_iso/host/common/include/common/app/gap.h @@ -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 { diff --git a/components/bt/esp_ble_iso/host/common/include/common/conn.h b/components/bt/esp_ble_iso/host/common/include/common/conn.h index a924df21942..a456c47cb09 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/conn.h +++ b/components/bt/esp_ble_iso/host/common/include/common/conn.h @@ -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); diff --git a/components/bt/esp_ble_iso/host/common/include/common/gatt.h b/components/bt/esp_ble_iso/host/common/include/common/gatt.h index 28dc6dc4bbb..d4c2aaabc35 100644 --- a/components/bt/esp_ble_iso/host/common/include/common/gatt.h +++ b/components/bt/esp_ble_iso/host/common/include/common/gatt.h @@ -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 diff --git a/components/bt/esp_ble_iso/host/common/iso.c b/components/bt/esp_ble_iso/host/common/iso.c index b1047b99a28..31e1dd0cd5d 100644 --- a/components/bt/esp_ble_iso/host/common/iso.c +++ b/components/bt/esp_ble_iso/host/common/iso.c @@ -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); diff --git a/components/bt/esp_ble_iso/host/common/scan.c b/components/bt/esp_ble_iso/host/common/scan.c index f0d1f663282..a07686d9ba4 100644 --- a/components/bt/esp_ble_iso/host/common/scan.c +++ b/components/bt/esp_ble_iso/host/common/scan.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -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; diff --git a/components/bt/esp_ble_iso/host/iso/iso.c b/components/bt/esp_ble_iso/host/iso/iso.c index edbd6735540..efb0352d6ea 100644 --- a/components/bt/esp_ble_iso/host/iso/iso.c +++ b/components/bt/esp_ble_iso/host/iso/iso.c @@ -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 */ diff --git a/components/bt/esp_ble_iso/host/utils/timer.c b/components/bt/esp_ble_iso/host/utils/timer.c index 49a5fe13c33..781de09f890 100644 --- a/components/bt/esp_ble_iso/host/utils/timer.c +++ b/components/bt/esp_ble_iso/host/utils/timer.c @@ -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; diff --git a/components/bt/esp_ble_iso/host/utils/utils.c b/components/bt/esp_ble_iso/host/utils/utils.c index c1bb2e1da2b..0a300567a12 100644 --- a/components/bt/esp_ble_iso/host/utils/utils.c +++ b/components/bt/esp_ble_iso/host/utils/utils.c @@ -7,16 +7,27 @@ #include #include +#include +#include #include #include +#include #include #include #include -#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 */ diff --git a/components/bt/esp_ble_iso/include/subsys/bluetooth/host/hci_core.h b/components/bt/esp_ble_iso/include/subsys/bluetooth/host/hci_core.h index af742f9ee5d..9eb2c4b92bc 100644 --- a/components/bt/esp_ble_iso/include/subsys/bluetooth/host/hci_core.h +++ b/components/bt/esp_ble_iso/include/subsys/bluetooth/host/hci_core.h @@ -9,6 +9,8 @@ #pragma once +#include + /* 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); }; diff --git a/components/bt/esp_ble_iso/include/zephyr/bluetooth/gatt.h b/components/bt/esp_ble_iso/include/zephyr/bluetooth/gatt.h index 7a0b8c325d8..dddb44e6179 100644 --- a/components/bt/esp_ble_iso/include/zephyr/bluetooth/gatt.h +++ b/components/bt/esp_ble_iso/include/zephyr/bluetooth/gatt.h @@ -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, \ } /** diff --git a/components/bt/esp_ble_iso/include/zephyr/bluetooth/uuid.h b/components/bt/esp_ble_iso/include/zephyr/bluetooth/uuid.h index 7f9e00a700f..ca9e4e95075 100644 --- a/components/bt/esp_ble_iso/include/zephyr/bluetooth/uuid.h +++ b/components/bt/esp_ble_iso/include/zephyr/bluetooth/uuid.h @@ -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 */ diff --git a/components/bt/esp_ble_iso/include/zephyr/kernel.h b/components/bt/esp_ble_iso/include/zephyr/kernel.h index 840868b04da..b3ff4259442 100644 --- a/components/bt/esp_ble_iso/include/zephyr/kernel.h +++ b/components/bt/esp_ble_iso/include/zephyr/kernel.h @@ -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 */ diff --git a/examples/bluetooth/esp_ble_audio/common_components/example_utils/ble_audio_example_utils.c b/examples/bluetooth/esp_ble_audio/common_components/example_utils/ble_audio_example_utils.c index 7a6fca06de7..b70c4725ffc 100644 --- a/examples/bluetooth/esp_ble_audio/common_components/example_utils/ble_audio_example_utils.c +++ b/examples/bluetooth/esp_ble_audio/common_components/example_utils/ble_audio_example_utils.c @@ -461,7 +461,7 @@ int example_audio_tx_scheduler_start(example_audio_tx_scheduler_t *scheduler, assert(scheduler); assert(period_us > 0); - return k_work_schedule_periodic(&scheduler->timer, (uint32_t)(period_us / 1000)); + return k_work_schedule_periodic_us(&scheduler->timer, period_us); } int example_audio_tx_scheduler_stop(example_audio_tx_scheduler_t *scheduler) diff --git a/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c b/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c index 4471171ae1b..2828bfb3c08 100644 --- a/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c +++ b/examples/bluetooth/esp_ble_audio/tmap/bmr/main/bap_broadcast_sink.c @@ -225,10 +225,9 @@ void bap_broadcast_pa_sync(esp_ble_audio_gap_app_event_t *event) { int err; - pa_syncing = false; /* Mark PA sync as completed */ - if (event->pa_sync.status) { ESP_LOGE(TAG, "PA sync failed, status %d", event->pa_sync.status); + pa_syncing = false; /* attempt failed; let a fresh scan report retry */ return; } @@ -260,6 +259,7 @@ void bap_broadcast_pa_lost(esp_ble_audio_gap_app_event_t *event) sync_handle, event->pa_sync_lost.reason); sync_handle = PA_SYNC_HANDLE_INIT; + pa_syncing = false; /* allow the rescan below to sync a fresh broadcaster */ if (broadcast_sink != NULL) { esp_ble_audio_bap_broadcast_sink_delete(broadcast_sink); diff --git a/examples/bluetooth/esp_ble_iso/common_components/example_utils/ble_iso_example_utils.c b/examples/bluetooth/esp_ble_iso/common_components/example_utils/ble_iso_example_utils.c index 8c244d907a0..51f70918bde 100644 --- a/examples/bluetooth/esp_ble_iso/common_components/example_utils/ble_iso_example_utils.c +++ b/examples/bluetooth/esp_ble_iso/common_components/example_utils/ble_iso_example_utils.c @@ -67,7 +67,7 @@ int example_iso_tx_scheduler_start(example_iso_tx_scheduler_t *scheduler, assert(scheduler); assert(period_us > 0); - return k_work_schedule_periodic(&scheduler->timer, (uint32_t)(period_us / 1000)); + return k_work_schedule_periodic_us(&scheduler->timer, period_us); } int example_iso_tx_scheduler_stop(example_iso_tx_scheduler_t *scheduler)