Merge branch 'idf/ble_audio_fixes' into 'master'

fix(ble_audio): Miscellaneous fixes for ISO & LE Audio (stage 2)

See merge request espressif/esp-idf!51158
This commit is contained in:
Island
2026-08-05 10:47:55 +08:00
113 changed files with 4547 additions and 2104 deletions
@@ -161,13 +161,25 @@ endif()
if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT)
list(APPEND ble_audio_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/host/services"
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots"
)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/ots_l2cap.c"
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/l2cap.c"
)
if(CONFIG_BT_NIMBLE_ENABLED)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/nimble/l2cap.c"
)
else()
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/adapter/bluedroid/l2cap.c"
)
endif()
if(CONFIG_BT_OTS)
list(APPEND ble_audio_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/services/ots/ots_oacp.c"
+3
View File
@@ -6,6 +6,9 @@
config BT_AUDIO
bool # hidden
# esp_ble_audio needs esp_ble_iso's headers (<zephyr/*>) and symbols
# (bt_gatt_*/bt_conn_*/k_*), and both are gated on BT_ISO.
select BT_ISO
help
This option enables Bluetooth Audio support. The specific
features that are available may depend on other features
@@ -6,11 +6,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include "esp_ble_audio_bap_api.h"
#if CONFIG_BT_BAP_UNICAST_SERVER
esp_err_t esp_ble_audio_bap_unicast_server_register(const esp_ble_audio_bap_unicast_server_register_param_t *param)
{
esp_err_t ret = ESP_OK;
int err;
if (param == NULL ||
@@ -20,20 +23,26 @@ esp_err_t esp_ble_audio_bap_unicast_server_register(const esp_ble_audio_bap_unic
return ESP_ERR_INVALID_ARG;
}
err = bt_bap_unicast_server_register_safe(param);
bt_le_host_lock();
err = bt_bap_unicast_server_register(param);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_ascs_init();
if (err) {
bt_bap_unicast_server_unregister_safe();
return ESP_FAIL;
bt_bap_unicast_server_unregister();
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_bap_unicast_server_unregister(void)
@@ -150,6 +159,8 @@ esp_err_t esp_ble_audio_bap_unicast_group_reconfig(esp_ble_audio_bap_unicast_gro
int err;
if (unicast_group == NULL || param == NULL ||
param->params == NULL ||
param->params_count == 0 ||
param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
return ESP_ERR_INVALID_ARG;
}
@@ -727,22 +738,29 @@ esp_err_t esp_ble_audio_bap_broadcast_sink_delete(esp_ble_audio_bap_broadcast_si
#if CONFIG_BT_BAP_SCAN_DELEGATOR
esp_err_t esp_ble_audio_bap_scan_delegator_register(esp_ble_audio_bap_scan_delegator_cb_t *cb)
{
esp_err_t ret = ESP_OK;
int err;
err = bt_bap_scan_delegator_register_safe(cb);
bt_le_host_lock();
err = bt_bap_scan_delegator_register(cb);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_bass_init();
if (err) {
bt_bap_scan_delegator_unregister_safe();
return ESP_FAIL;
bt_bap_scan_delegator_unregister();
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_bap_scan_delegator_unregister(void)
@@ -798,7 +816,8 @@ esp_err_t esp_ble_audio_bap_scan_delegator_add_src(const esp_ble_audio_bap_scan_
param->sid > BT_GAP_SID_MAX ||
param->pa_state > ESP_BLE_AUDIO_BAP_PA_STATE_NO_PAST ||
param->encrypt_state > ESP_BLE_AUDIO_BAP_BIG_ENC_STATE_BAD_CODE ||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
return ESP_ERR_INVALID_ARG;
}
@@ -818,7 +837,8 @@ esp_err_t esp_ble_audio_bap_scan_delegator_mod_src(const esp_ble_audio_bap_scan_
if (param == NULL ||
param->broadcast_id > ESP_BLE_AUDIO_BROADCAST_ID_MAX ||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
return ESP_ERR_INVALID_ARG;
}
@@ -959,7 +979,8 @@ esp_err_t esp_ble_audio_bap_broadcast_assistant_add_src(uint16_t conn_handle,
param->broadcast_id > ESP_BLE_AUDIO_BROADCAST_ID_MAX ||
param->addr.type > BT_ADDR_LE_RANDOM ||
param->adv_sid > BT_GAP_SID_MAX ||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
return ESP_ERR_INVALID_ARG;
}
@@ -989,7 +1010,8 @@ esp_err_t esp_ble_audio_bap_broadcast_assistant_mod_src(uint16_t conn_handle,
int err;
if (param == NULL ||
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) {
param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS ||
(param->num_subgroups > 0 && param->subgroups == NULL)) {
return ESP_ERR_INVALID_ARG;
}
@@ -1288,7 +1310,7 @@ esp_err_t esp_ble_audio_bap_base_foreach_subgroup(const esp_ble_audio_bap_base_t
}
err = bt_bap_base_foreach_subgroup(base, func, user_data);
if (err) {
if (err && err != -ECANCELED) {
return ESP_FAIL;
}
@@ -1411,7 +1433,7 @@ esp_err_t esp_ble_audio_bap_base_subgroup_foreach_bis(const esp_ble_audio_bap_ba
}
err = bt_bap_base_subgroup_foreach_bis(subgroup, func, user_data);
if (err) {
if (err && err != -ECANCELED) {
return ESP_FAIL;
}
@@ -128,7 +128,10 @@ esp_err_t esp_ble_audio_cap_unicast_group_create(const esp_ble_audio_cap_unicast
{
int err;
if (param == NULL || unicast_group == NULL) {
if (param == NULL || unicast_group == NULL ||
param->params == NULL ||
param->params_count == 0 ||
param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
return ESP_ERR_INVALID_ARG;
}
@@ -145,7 +148,10 @@ esp_err_t esp_ble_audio_cap_unicast_group_reconfig(esp_ble_audio_cap_unicast_gro
{
int err;
if (unicast_group == NULL || param == NULL) {
if (unicast_group == NULL || param == NULL ||
param->params == NULL ||
param->params_count == 0 ||
param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
return ESP_ERR_INVALID_ARG;
}
@@ -255,7 +261,7 @@ esp_err_t esp_ble_audio_cap_initiator_unicast_audio_start(const esp_ble_audio_ca
int err;
if (param == NULL || param->count == 0 || param->stream_params == NULL ||
param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
param->count > (2 * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)) {
return ESP_ERR_INVALID_ARG;
}
@@ -272,7 +278,7 @@ esp_err_t esp_ble_audio_cap_initiator_unicast_audio_update(const esp_ble_audio_c
int err;
if (param == NULL || param->count == 0 || param->stream_params == NULL ||
param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
param->count > (2 * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)) {
return ESP_ERR_INVALID_ARG;
}
@@ -289,7 +295,7 @@ esp_err_t esp_ble_audio_cap_initiator_unicast_audio_stop(const esp_ble_audio_cap
int err;
if (param == NULL || param->count == 0 || param->streams == NULL ||
param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) {
param->count > (2 * CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)) {
return ESP_ERR_INVALID_ARG;
}
@@ -299,7 +299,7 @@ esp_err_t esp_ble_audio_codec_cfg_set_val(esp_ble_audio_codec_cfg_t *codec_cfg,
if (codec_cfg == NULL || codec_cfg->data == NULL ||
data == NULL ||
data_len == 0 || data_len > UINT8_MAX) {
data_len == 0 || data_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -357,7 +357,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_val(esp_ble_audio_codec_cfg_t *codec_
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
data == NULL ||
data_len == 0 || data_len > UINT8_MAX) {
data_len == 0 || data_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -490,7 +490,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_program_info(esp_ble_audio_codec_cfg_
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
program_info == NULL ||
program_info_len == 0 || program_info_len > UINT8_MAX) {
program_info_len == 0 || program_info_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -565,7 +565,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_ccid_list(esp_ble_audio_codec_cfg_t *
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
ccid_list == NULL ||
ccid_list_len == 0 || ccid_list_len > UINT8_MAX) {
ccid_list_len == 0 || ccid_list_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -643,7 +643,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_program_info_uri(esp_ble_audio_codec_
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
program_info_uri == NULL ||
program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX) {
program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -795,7 +795,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_broadcast_name(esp_ble_audio_codec_cf
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
broadcast_name == NULL ||
broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX) {
broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -836,7 +836,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_extended(esp_ble_audio_codec_cfg_t *c
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
extended_meta == NULL ||
extended_meta_len == 0 || extended_meta_len > UINT8_MAX) {
extended_meta_len == 0 || extended_meta_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -877,7 +877,7 @@ esp_err_t esp_ble_audio_codec_cfg_meta_set_vendor(esp_ble_audio_codec_cfg_t *cod
if (codec_cfg == NULL || codec_cfg->meta == NULL ||
vendor_meta == NULL ||
vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX) {
vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -918,7 +918,7 @@ esp_err_t esp_ble_audio_codec_cap_set_val(esp_ble_audio_codec_cap_t *codec_cap,
if (codec_cap == NULL || codec_cap->data == NULL ||
data == NULL ||
data_len == 0 || data_len > UINT8_MAX) {
data_len == 0 || data_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1181,7 +1181,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_val(esp_ble_audio_codec_cap_t *codec_
if (codec_cap == NULL || codec_cap->meta == NULL ||
data == NULL ||
data_len == 0 || data_len > UINT8_MAX) {
data_len == 0 || data_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1313,7 +1313,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_program_info(esp_ble_audio_codec_cap_
if (codec_cap == NULL || codec_cap->meta == NULL ||
program_info == NULL ||
program_info_len == 0 || program_info_len > UINT8_MAX) {
program_info_len == 0 || program_info_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1388,7 +1388,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_ccid_list(esp_ble_audio_codec_cap_t *
if (codec_cap == NULL || codec_cap->meta == NULL ||
ccid_list == NULL ||
ccid_list_len == 0 || ccid_list_len > UINT8_MAX) {
ccid_list_len == 0 || ccid_list_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1466,7 +1466,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_program_info_uri(esp_ble_audio_codec_
if (codec_cap == NULL || codec_cap->meta == NULL ||
program_info_uri == NULL ||
program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX) {
program_info_uri_len == 0 || program_info_uri_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1618,7 +1618,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_broadcast_name(esp_ble_audio_codec_ca
if (codec_cap == NULL || codec_cap->meta == NULL ||
broadcast_name == NULL ||
broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX) {
broadcast_name_len == 0 || broadcast_name_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1659,7 +1659,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_extended(esp_ble_audio_codec_cap_t *c
if (codec_cap == NULL || codec_cap->meta == NULL ||
extended_meta == NULL ||
extended_meta_len == 0 || extended_meta_len > UINT8_MAX) {
extended_meta_len == 0 || extended_meta_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -1700,7 +1700,7 @@ esp_err_t esp_ble_audio_codec_cap_meta_set_vendor(esp_ble_audio_codec_cap_t *cod
if (codec_cap == NULL || codec_cap->meta == NULL ||
vendor_meta == NULL ||
vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX) {
vendor_meta_len == 0 || vendor_meta_len > UINT8_MAX - 1) {
return ESP_ERR_INVALID_ARG;
}
@@ -65,7 +65,7 @@ esp_err_t esp_ble_audio_gattc_disc_start(uint16_t conn_handle)
return ESP_OK;
}
void esp_ble_audio_gap_app_post_event(uint8_t type, void *param)
void esp_ble_audio_gap_app_post_event(uint16_t type, void *param)
{
bt_le_gap_app_post_event(type, param);
}
@@ -276,7 +276,7 @@ esp_err_t esp_ble_audio_csip_set_coordinator_lock(const esp_ble_audio_csip_set_c
{
int err;
if (members == NULL || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
if (members == NULL || count == 0 || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -294,7 +294,7 @@ esp_err_t esp_ble_audio_csip_set_coordinator_release(const esp_ble_audio_csip_se
{
int err;
if (members == NULL || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
if (members == NULL || count == 0 || count > CONFIG_BT_MAX_CONN || set_info == NULL) {
return ESP_ERR_INVALID_ARG;
}
@@ -94,27 +94,30 @@ static bool valid_gmap_features(esp_ble_audio_gmap_role_t role,
if ((role & ESP_BLE_AUDIO_GMAP_ROLE_UGG) != 0) {
esp_ble_audio_gmap_ugg_feat_t ugg_feat = features.ugg_feat;
/* UGG TX (multiplex / 96kbps source) configures peer Sink ASEs → ASE_SNK.
* UGG RX (multisink) configures peer Source ASEs → ASE_SRC.
* (BT_AUDIO_TX/RX: client ASE_SNK = TX, ASE_SRC = RX.) */
if ((ugg_feat & ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTIPLEX) != 0 &&
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0) {
/* Cannot support ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTIPLEX with
* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0.
* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0.
*/
return false;
}
if ((ugg_feat & ESP_BLE_AUDIO_GMAP_UGG_FEAT_96KBPS_SOURCE) != 0 &&
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0) {
/* Cannot support ESP_BLE_AUDIO_GMAP_UGG_FEAT_96KBPS_SOURCE with
* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0.
* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0.
*/
return false;
}
if ((ugg_feat & ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTISINK) != 0 &&
(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT < 2 ||
(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT < 2 ||
CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2)) {
/* Cannot support ESP_BLE_AUDIO_GMAP_UGG_FEAT_MULTISINK with
* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT or
* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT or
* CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2.
*/
return false;
@@ -144,28 +144,35 @@ esp_err_t esp_ble_audio_has_client_preset_prev(esp_ble_audio_has_t *has, bool sy
#if CONFIG_BT_HAS
esp_err_t esp_ble_audio_has_register(const esp_ble_audio_has_features_param_t *features)
{
esp_err_t ret = ESP_OK;
int err;
if (features == NULL) {
return ESP_ERR_INVALID_ARG;
}
err = bt_has_register_safe(features);
bt_le_host_lock();
err = bt_has_register(features);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_has_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
/* TODO: rollback register once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_has_preset_register(const esp_ble_audio_has_preset_register_param_t *param)
@@ -288,7 +288,8 @@ esp_err_t esp_ble_audio_media_proxy_ctrl_set_next_track_id(esp_ble_audio_media_p
{
int err;
if (player == NULL || ESP_BLE_AUDIO_MCS_VALID_OBJ_ID(id) == false) {
if (player == NULL || (IS_ENABLED(CONFIG_BT_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) &&
ESP_BLE_AUDIO_MCS_VALID_OBJ_ID(id) == false)) {
return ESP_ERR_INVALID_ARG;
}
@@ -415,12 +416,19 @@ esp_err_t esp_ble_audio_media_proxy_ctrl_get_media_state(esp_ble_audio_media_pla
return ESP_OK;
}
#define MCS_VALID_OP(opcode) \
(IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PLAY, ESP_BLE_AUDIO_MCS_OPC_STOP) || \
(opcode == ESP_BLE_AUDIO_MCS_OPC_MOVE_RELATIVE) || \
IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PREV_SEGMENT, ESP_BLE_AUDIO_MCS_OPC_GOTO_SEGMENT) || \
IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PREV_TRACK, ESP_BLE_AUDIO_MCS_OPC_GOTO_TRACK) || \
IN_RANGE((opcode), ESP_BLE_AUDIO_MCS_OPC_PREV_GROUP, ESP_BLE_AUDIO_MCS_OPC_GOTO_GROUP))
esp_err_t esp_ble_audio_media_proxy_ctrl_send_command(esp_ble_audio_media_player_t *player,
const esp_ble_audio_mpl_cmd_t *command)
{
int err;
if (player == NULL || command == NULL) {
if (player == NULL || command == NULL || MCS_VALID_OP(command->opcode) == false) {
return ESP_ERR_INVALID_ARG;
}
@@ -453,7 +461,9 @@ esp_err_t esp_ble_audio_media_proxy_ctrl_send_search(esp_ble_audio_media_player_
{
int err;
if (player == NULL || search == NULL) {
if (player == NULL || search == NULL ||
IN_RANGE(search->len, ESP_BLE_AUDIO_SEARCH_LEN_MIN,
ESP_BLE_AUDIO_SEARCH_LEN_MAX) == false) {
return ESP_ERR_INVALID_ARG;
}
@@ -561,24 +571,31 @@ 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)
{
esp_err_t ret = ESP_OK;
int err;
err = bt_media_proxy_pl_init_safe();
bt_le_host_lock();
err = bt_media_proxy_pl_init();
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if CONFIG_BT_MCS && BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_media_proxy_pl_init();
if (err) {
/* TODO: rollback pl_init_safe once lib exposes an undo API;
/* TODO: rollback pl_init once lib exposes an undo API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#endif /* CONFIG_BT_MCS && BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_media_proxy_pl_set_player_name(char *name)
@@ -10,6 +10,7 @@
#if CONFIG_BT_MICP_MIC_DEV
esp_err_t esp_ble_audio_micp_mic_dev_register(esp_ble_audio_micp_mic_dev_register_param_t *param)
{
esp_err_t ret = ESP_OK;
int err;
if (param == NULL ||
@@ -17,22 +18,28 @@ esp_err_t esp_ble_audio_micp_mic_dev_register(esp_ble_audio_micp_mic_dev_registe
return ESP_ERR_INVALID_ARG;
}
err = bt_micp_mic_dev_register_safe(param);
bt_le_host_lock();
err = bt_micp_mic_dev_register(param);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_micp_mic_dev_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
/* TODO: rollback register once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_micp_mic_dev_included_get(esp_ble_audio_micp_included_t *included)
@@ -16,7 +16,7 @@ esp_err_t esp_ble_audio_pbp_get_announcement(const uint8_t meta[], size_t meta_l
int err;
if (pba_data_buf == NULL ||
pba_data_buf->size < (meta_len + ESP_BLE_AUDIO_PBP_MIN_PBA_SIZE)) {
net_buf_simple_tailroom(pba_data_buf) < (meta_len + ESP_BLE_AUDIO_PBP_MIN_PBA_SIZE)) {
return ESP_ERR_INVALID_ARG;
}
@@ -337,27 +337,34 @@ static bool valid_register_param(const esp_ble_audio_tbs_register_param_t *param
esp_err_t esp_ble_audio_tbs_register_bearer(const esp_ble_audio_tbs_register_param_t *param,
uint8_t *bearer_index)
{
int ret;
esp_err_t ret = ESP_OK;
int err;
if (param == NULL || valid_register_param(param) == false || bearer_index == NULL) {
return ESP_ERR_INVALID_ARG;
}
ret = bt_tbs_register_bearer_safe(param);
if (ret < 0) {
return ESP_FAIL;
bt_le_host_lock();
err = bt_tbs_register_bearer(param);
if (err < 0) {
ret = ESP_FAIL;
goto end;
}
*bearer_index = ret;
*bearer_index = err;
#if BLE_AUDIO_SVC_DEFERRED_ADD
if (param->gtbs ? bt_le_gtbs_init() : bt_le_tbs_init()) {
bt_tbs_unregister_bearer_safe(*bearer_index);
return ESP_FAIL;
bt_tbs_unregister_bearer(*bearer_index);
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_tbs_unregister_bearer(uint8_t bearer_index)
@@ -11,24 +11,31 @@
#if CONFIG_BT_TMAP
esp_err_t esp_ble_audio_tmap_register(esp_ble_audio_tmap_role_t role)
{
esp_err_t ret = ESP_OK;
int err;
err = bt_tmap_register_safe(role);
bt_le_host_lock();
err = bt_tmap_register(role);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_tmas_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
/* TODO: rollback register once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_tmap_discover(uint16_t conn_handle,
@@ -38,6 +45,10 @@ esp_err_t esp_ble_audio_tmap_discover(uint16_t conn_handle,
void *conn;
int err;
if (tmap_cb == NULL) {
return ESP_ERR_INVALID_ARG;
}
bt_le_host_lock();
conn = bt_le_acl_conn_find(conn_handle);
@@ -26,6 +26,7 @@ esp_err_t esp_ble_audio_vcp_vol_rend_included_get(esp_ble_audio_vcp_included_t *
esp_err_t esp_ble_audio_vcp_vol_rend_register(esp_ble_audio_vcp_vol_rend_register_param_t *param)
{
esp_err_t ret = ESP_OK;
int err;
if (param == NULL) {
@@ -44,22 +45,28 @@ esp_err_t esp_ble_audio_vcp_vol_rend_register(esp_ble_audio_vcp_vol_rend_registe
return ESP_ERR_INVALID_ARG;
}
err = bt_vcp_vol_rend_register_safe(param);
bt_le_host_lock();
err = bt_vcp_vol_rend_register(param);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#if BLE_AUDIO_SVC_DEFERRED_ADD
err = bt_le_vcp_vol_rend_init();
if (err) {
/* TODO: rollback register_safe once lib exposes an unregister API;
/* TODO: rollback register once lib exposes an unregister API;
* retry will hit -EALREADY. Only reachable on GATT alloc failure.
*/
return ESP_FAIL;
ret = ESP_FAIL;
goto end;
}
#endif /* BLE_AUDIO_SVC_DEFERRED_ADD */
return ESP_OK;
end:
bt_le_host_unlock();
return ret;
}
esp_err_t esp_ble_audio_vcp_vol_rend_set_step(uint8_t volume_step)
@@ -126,7 +126,7 @@ typedef struct {
* @param type Event type.
* @param param Event parameters.
*/
void esp_ble_audio_gap_app_post_event(uint8_t type, void *param);
void esp_ble_audio_gap_app_post_event(uint16_t type, void *param);
#if !CONFIG_BT_BLUEDROID_ENABLED
/**
@@ -76,13 +76,10 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc)
return -EINVAL;
}
inc_vocs_svc_count = vcp_included->vocs_cnt;
inc_aics_svc_count = vcp_included->aics_cnt;
bt_le_bluedroid_set_svc_in_progress(VOCS_IN_PROGRESS);
/* VCS may include zero or more instances of VOCS */
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
for (size_t i = 0; i < vcp_included->vocs_cnt; i++) {
inc_vocs_insts[i].svc_p = lib_vocs_svc_get(vcp_included->vocs[i]);
if (!inc_vocs_insts[i].svc_p) {
LOG_ERR("[B]VocsSvcGetFail[%u]", i);
@@ -101,7 +98,7 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc)
bt_le_bluedroid_set_svc_in_progress(AICS_IN_PROGRESS);
/* VCS may include zero or more instances of AICS */
for (size_t i = 0; i < inc_aics_svc_count; i++) {
for (size_t i = 0; i < vcp_included->aics_cnt; i++) {
inc_aics_insts[i].svc_p = lib_aics_svc_get(vcp_included->aics[i]);
if (!inc_aics_insts[i].svc_p) {
LOG_ERR("[B]AicsSvcGetFail[%u]", i);
@@ -116,6 +113,12 @@ int bt_le_bluedroid_vcs_init(void *vcp_inc)
return err;
}
}
/* Commit counts only after every instance initialized — mid-loop
* failure leaves them at 0 (reset on entry) so vcs_start() never
* iterates partially-initialized entries. */
inc_vocs_svc_count = vcp_included->vocs_cnt;
inc_aics_svc_count = vcp_included->aics_cnt;
}
bt_le_bluedroid_set_svc_in_progress(VCS_IN_PROGRESS);
@@ -173,15 +173,14 @@ static void inc_svc_add_cb(uint16_t service_id, uint16_t attr_id, uint8_t status
int result = status;
if (service_id != svc_handle) {
/* Stale after timeout; take already failed — ignore without give. */
LOG_ERR("[B]IncSvcAddMismatch[%u][%u][%u]",
svc_in_progress, service_id, svc_handle);
result = -1;
goto end;
return;
}
inc_svc_handle = attr_id;
end:
bt_le_bluedroid_gatts_sem_give(result);
}
@@ -190,9 +189,8 @@ static void chrc_add_cb(uint16_t service_id, uint16_t attr_id,
{
int result = status;
if (service_id != svc_handle || ((tBT_UUID *)uuid)->len != 2) {
/* uuid16 is only meaningful when len == 2; for 32/128-bit UUIDs the
* union access would log the first 2 bytes of a wider value. */
if (service_id != svc_handle) {
/* Stale after timeout; take already failed — ignore without give. */
if (((tBT_UUID *)uuid)->len == 2) {
LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][2][0x%04x]",
svc_in_progress, service_id, svc_handle,
@@ -202,6 +200,13 @@ static void chrc_add_cb(uint16_t service_id, uint16_t attr_id,
svc_in_progress, service_id, svc_handle,
((tBT_UUID *)uuid)->len);
}
return;
}
if (((tBT_UUID *)uuid)->len != 2) {
LOG_ERR("[B]ChrcAddMismatch[%u][%u][%u][%u][nonU16]",
svc_in_progress, service_id, svc_handle,
((tBT_UUID *)uuid)->len);
result = -1;
goto end;
}
@@ -214,14 +219,13 @@ end:
static void svc_start_cb(uint16_t service_id, uint8_t status)
{
int result = status;
if (service_id != svc_handle) {
/* Stale after timeout; take already failed — ignore without give. */
LOG_ERR("[B]SvcStartMismatch[%u][%u]", service_id, svc_handle);
result = -1;
return;
}
bt_le_bluedroid_gatts_sem_give(result);
bt_le_bluedroid_gatts_sem_give(status);
}
static struct gatts_svc_cb svc_cb = {
@@ -456,7 +460,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
uint8_t inst_id;
tBT_UUID uuid;
assert(svc);
BT_LE_ASSERT(svc);
for (size_t i = 0; i < svc->attr_count; i++) {
curr_attr = &svc->attrs[i];
@@ -470,7 +474,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
return -1;
}
assert(curr_attr->user_data);
BT_LE_ASSERT(curr_attr->user_data);
bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid);
inst_id = get_svc_inst_id(uuid.uu.uuid16);
@@ -496,7 +500,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
return -1;
}
assert(curr_attr->user_data);
BT_LE_ASSERT(curr_attr->user_data);
bt_le_bluedroid_gatt_uuid_convert(curr_attr->user_data, &uuid);
inst_id = get_svc_inst_id(uuid.uu.uuid16);
@@ -561,7 +565,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
next_attr = &svc->attrs[i + 1];
perm = bt_le_bluedroid_gatt_perm_convert(next_attr->perm);
assert(curr_attr->user_data);
BT_LE_ASSERT(curr_attr->user_data);
chrc = curr_attr->user_data;
bt_le_bluedroid_gatt_uuid_convert(chrc->uuid, &uuid);
@@ -612,7 +616,7 @@ int bt_le_bluedroid_svc_init(struct bt_gatt_service *svc)
int bt_le_bluedroid_svc_start(struct bt_gatt_service *svc)
{
assert(svc);
BT_LE_ASSERT(svc);
svc_handle = svc->attrs[0].handle;
@@ -62,7 +62,7 @@ static void ascs_svc_add_ase_cp_chr(struct ble_gatt_chr_def *chr)
chr->arg = NULL;
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC;
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
chr->min_key_size = 16;
chr->val_handle = &ase_control_point_handle;
}
@@ -79,7 +79,7 @@ static void ascs_svc_add_ase_snk_chr(struct ble_gatt_chr_def *chrs)
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
chr->arg = UINT_TO_POINTER(i);
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
chr->min_key_size = 16;
chr->val_handle = &ase_snk_handle[i];
}
@@ -98,7 +98,7 @@ static void ascs_svc_add_ase_src_chr(struct ble_gatt_chr_def *chrs)
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
chr->arg = UINT_TO_POINTER(i);
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
chr->min_key_size = 16;
chr->val_handle = &ase_src_handle[i];
}
@@ -128,7 +128,7 @@ int bt_le_nimble_ascs_attr_handle_set(void)
return -ENODEV;
}
assert(ase_control_point_handle >= 2);
BT_LE_ASSERT(ase_control_point_handle >= 2);
start_handle = ase_control_point_handle - 2; /* server attr handle & char def handle */
#if CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0
end_handle = ase_src_handle[CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT - 1] + 1; /* cccd attr handle */
@@ -202,6 +202,7 @@ static int ascs_svc_check(void)
int bt_le_nimble_ascs_init(void)
{
bool ascs_added = false;
uint16_t chr_count;
int rc;
@@ -211,7 +212,7 @@ int bt_le_nimble_ascs_init(void)
LOG_DBG("[N]AscsInit[%u]", chr_count);
gatt_svc_ascs[0].characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
assert(gatt_svc_ascs[0].characteristics);
BT_LE_ASSERT(gatt_svc_ascs[0].characteristics);
ascs_svc_add_ase_cp_chr((void *)(gatt_svc_ascs[0].characteristics + 0));
@@ -234,6 +235,7 @@ int bt_le_nimble_ascs_init(void)
LOG_ERR("[N]AscsAddSvcsFail[%d]", rc);
goto free;
}
ascs_added = true;
rc = ascs_svc_check();
if (rc) {
@@ -243,7 +245,12 @@ int bt_le_nimble_ascs_init(void)
return 0;
free:
free((void *)gatt_svc_ascs[0].characteristics);
gatt_svc_ascs[0].characteristics = NULL;
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (!ascs_added) {
free((void *)gatt_svc_ascs[0].characteristics);
gatt_svc_ascs[0].characteristics = NULL;
}
return rc;
}
@@ -75,7 +75,7 @@ static void bass_svc_add_recv_state_chr(struct ble_gatt_chr_def *chrs)
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
chr->arg = UINT_TO_POINTER(i);
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
chr->min_key_size = 16;
chr->val_handle = &bass_recv_state_handle[i];
}
@@ -104,7 +104,7 @@ int bt_le_nimble_bass_attr_handle_set(void)
return -ENODEV;
}
assert(bass_control_point_handle >= 2);
BT_LE_ASSERT(bass_control_point_handle >= 2);
start_handle = bass_control_point_handle - 2; /* server attr handle & char def handle */
end_handle = bass_recv_state_handle[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT - 1] + 1; /* cccd attr handle */
@@ -154,7 +154,7 @@ static int bass_svc_check(void)
for (size_t i = 0; i < bass_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(bass_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -172,6 +172,7 @@ static int bass_svc_check(void)
int bt_le_nimble_bass_init(void)
{
bool bass_added = false;
uint16_t chr_count;
int rc;
@@ -181,7 +182,7 @@ int bt_le_nimble_bass_init(void)
LOG_DBG("[N]BassInit[%u]", chr_count);
gatt_svc_bass->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
assert(gatt_svc_bass->characteristics);
BT_LE_ASSERT(gatt_svc_bass->characteristics);
bass_svc_add_control_point_chr((void *)(gatt_svc_bass->characteristics + 0));
@@ -198,6 +199,7 @@ int bt_le_nimble_bass_init(void)
LOG_ERR("[N]BassAddSvcsFail[%d]", rc);
goto free;
}
bass_added = true;
rc = bass_svc_check();
if (rc) {
@@ -207,7 +209,12 @@ int bt_le_nimble_bass_init(void)
return 0;
free:
free((void *)gatt_svc_bass->characteristics);
gatt_svc_bass->characteristics = NULL;
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (!bass_added) {
free((void *)gatt_svc_bass->characteristics);
gatt_svc_bass->characteristics = NULL;
}
return rc;
}
@@ -40,7 +40,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE
#define CSIS_CHR_FLAGS_SIRK \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#else /* CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE */
#define CSIS_CHR_FLAGS_SIRK \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
@@ -48,14 +48,14 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#if CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE
#define CSIS_CHR_FLAGS_SET_SIZE \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#else /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */
#define CSIS_CHR_FLAGS_SET_SIZE \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE */
#define CSIS_CHR_FLAGS_SET_LOCK \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE | \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
#define CSIS_CHR_FLAGS_RANK \
@@ -63,7 +63,7 @@ LOG_MODULE_REGISTER(LEA_CSIS, CONFIG_BT_ISO_LOG_LEVEL);
#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)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_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)
@@ -124,7 +124,7 @@ static int csis_svc_check(void)
struct ble_gatt_svc_def *csis = &gatt_svc_csis[i];
struct bt_gatt_service *svc = csis_insts[i].svc_p;
assert(svc);
BT_LE_ASSERT(svc);
for (const struct ble_gatt_chr_def *chr = csis->characteristics;
chr && chr->uuid; chr++) {
@@ -135,7 +135,7 @@ static int csis_svc_check(void)
for (size_t j = 0; j < svc->attr_count; j++) {
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -161,8 +161,8 @@ int bt_le_nimble_csis_attr_handle_set(void)
for (size_t i = 0; i < csis_svc_count; i++) {
struct bt_gatt_service *zsvc = csis_insts[i].svc_p;
assert(zsvc);
assert(csis_insts[i].sirk_handle >= 2);
BT_LE_ASSERT(zsvc);
BT_LE_ASSERT(csis_insts[i].sirk_handle >= 2);
/* SIRK is always the first characteristic, so its value handle anchors the range. */
start_handle = csis_insts[i].sirk_handle - 2; /* server attr handle & char def handle */
@@ -181,7 +181,7 @@ int bt_le_nimble_csis_attr_handle_set(void)
const struct bt_uuid_16 *uuid = (const struct bt_uuid_16 *)(zsvc->attrs + j)->uuid;
uint16_t chr_handle = 0;
if (uuid->uuid.type != BT_UUID_TYPE_16) {
if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16) {
continue;
}
@@ -246,7 +246,7 @@ static void csis_svc_init(struct csis_inst *inst,
svc->includes = NULL;
svc->characteristics = bt_le_ext_calloc(CSIS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
BT_LE_ASSERT(svc->characteristics);
/* Build the NimBLE characteristics from the ones actually present in the Zephyr
* service. Optional characteristics (set size, lock, rank) may be absent depending
@@ -255,7 +255,7 @@ static void csis_svc_init(struct csis_inst *inst,
for (size_t i = 0; i < zsvc->attr_count; i++) {
const struct bt_uuid_16 *uuid = (const struct bt_uuid_16 *)zsvc->attrs[i].uuid;
if (uuid->uuid.type != BT_UUID_TYPE_16) {
if (!uuid || uuid->uuid.type != BT_UUID_TYPE_16) {
continue;
}
@@ -292,11 +292,12 @@ static void csis_svc_init(struct csis_inst *inst,
* 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);
BT_LE_ASSERT(chr_cnt < CSIS_CHR_COUNT);
}
int bt_le_nimble_csis_init(void *svc, uint8_t count)
{
bool csis_added = false;
int rc;
LOG_DBG("[N]CsisInit[%u]", count);
@@ -325,6 +326,7 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count)
LOG_ERR("[N]CsisAddSvcsFail[%d]", rc);
goto free;
}
csis_added = true;
rc = csis_svc_check();
if (rc) {
@@ -334,9 +336,14 @@ int bt_le_nimble_csis_init(void *svc, uint8_t count)
return 0;
free:
for (size_t i = 0; i < csis_svc_count; i++) {
free((void *)gatt_svc_csis[i].characteristics);
gatt_svc_csis[i].characteristics = NULL;
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (!csis_added) {
for (size_t i = 0; i < csis_svc_count; i++) {
free((void *)gatt_svc_csis[i].characteristics);
gatt_svc_csis[i].characteristics = NULL;
}
}
csis_svc_count = 0;
return rc;
@@ -50,7 +50,7 @@ static int gmas_build_svc(struct bt_gatt_service *gmas_svc)
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) {
if (!u || u->uuid.type != BT_UUID_TYPE_16) {
prev_decl = false;
continue;
}
@@ -147,7 +147,7 @@ static int gmas_svc_check(void)
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) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -60,7 +60,7 @@ static void has_svc_add_features_chr(struct ble_gatt_chr_def *chr)
chr->arg = NULL;
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_HAS_FEATURES_NOTIFIABLE
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
#else /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC;
#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
@@ -78,9 +78,9 @@ static void has_svc_add_control_point_chr(struct ble_gatt_chr_def *chr)
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE |
BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY;
BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
#else /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC;
chr->flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
chr->min_key_size = 16;
chr->val_handle = &has_control_point_handle;
@@ -97,7 +97,7 @@ static void has_svc_add_preset_index_chr(struct ble_gatt_chr_def *chr)
chr->access_cb = bt_le_nimble_gatts_access_cb_safe;
chr->arg = NULL;
chr->descriptors = NULL; /* NULL if no descriptors. Do not include CCCD */
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC;
chr->flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC;
chr->min_key_size = 16;
chr->val_handle = &has_preset_index_handle;
}
@@ -125,9 +125,15 @@ int bt_le_nimble_has_attr_handle_set(void)
LOG_ERR("[N]HasSvcGetFail");
return -ENODEV;
}
assert(has_svc->attr_count > 0);
BT_LE_ASSERT(has_svc->attr_count > 0);
end_handle = start_handle + has_svc->attr_count - 1;
#if CONFIG_BT_HAS_ACTIVE_PRESET_INDEX
BT_LE_ASSERT(has_preset_index_handle >= 2);
end_handle = has_preset_index_handle + 1; /* cccd attr handle */
#else
BT_LE_ASSERT(has_control_point_handle >= 2);
end_handle = has_control_point_handle + 1; /* cccd attr handle */
#endif
LOG_DBG("[N]HasAttrHdlSet[%u][%u][%u]",
start_handle, end_handle, has_svc->attr_count);
@@ -175,7 +181,7 @@ static int has_svc_check(void)
for (size_t i = 0; i < has_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(has_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -193,6 +199,7 @@ static int has_svc_check(void)
int bt_le_nimble_has_init(void)
{
bool has_added = false;
uint8_t chr_count;
int rc;
@@ -206,7 +213,7 @@ int bt_le_nimble_has_init(void)
LOG_DBG("[N]HasInit[%u]", chr_count);
gatt_svc_has->characteristics = bt_le_ext_calloc(chr_count, sizeof(struct ble_gatt_chr_def));
assert(gatt_svc_has->characteristics);
BT_LE_ASSERT(gatt_svc_has->characteristics);
has_svc_add_features_chr((void *)(gatt_svc_has->characteristics + 0));
@@ -227,6 +234,7 @@ int bt_le_nimble_has_init(void)
LOG_ERR("[N]HasAddSvcsFail[%d]", rc);
goto free;
}
has_added = true;
rc = has_svc_check();
if (rc) {
@@ -236,7 +244,12 @@ int bt_le_nimble_has_init(void)
return 0;
free:
free((void *)gatt_svc_has->characteristics);
gatt_svc_has->characteristics = NULL;
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (!has_added) {
free((void *)gatt_svc_has->characteristics);
gatt_svc_has->characteristics = NULL;
}
return rc;
}
@@ -72,10 +72,10 @@ LOG_MODULE_REGISTER(LEA_MCS, CONFIG_BT_ISO_LOG_LEVEL);
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
#define INC_OTS_CHR_FLAGS_ACTION_CP \
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC)
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#define INC_OTS_CHR_FLAGS_LIST_CP \
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC)
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_INDICATE | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_ots_svc_count;
@@ -146,7 +146,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_player_name_handle,
},
@@ -174,7 +174,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_NOTIFY,
.flags = BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_track_changed_handle,
}, {
@@ -182,7 +182,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_track_title_handle,
}, {
@@ -190,7 +190,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_track_duration_handle,
}, {
@@ -199,7 +199,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
.val_handle = &mcs_track_position_handle,
@@ -209,7 +209,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
.val_handle = &mcs_playback_speed_handle,
@@ -218,7 +218,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_seeking_speed_handle,
},
@@ -237,7 +237,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
.val_handle = &mcs_current_track_obj_id_handle,
@@ -247,7 +247,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
.val_handle = &mcs_next_track_obj_id_handle,
@@ -256,7 +256,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_parent_group_obj_id_handle,
}, {
@@ -265,7 +265,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
.val_handle = &mcs_current_group_obj_id_handle,
@@ -277,7 +277,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | \
BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
.val_handle = &mcs_playing_order_handle,
@@ -294,7 +294,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_media_state_handle,
}, {
@@ -303,7 +303,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC,
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_media_control_point_handle,
}, {
@@ -311,7 +311,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_media_control_opcodes_handle,
},
@@ -322,7 +322,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC,
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_search_control_point_handle,
}, {
@@ -330,7 +330,7 @@ static struct ble_gatt_svc_def gatt_svc_gmcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &mcs_search_results_obj_id_handle,
},
@@ -366,9 +366,9 @@ static int inc_ots_attr_handle_set(void)
uint16_t start_handle;
uint16_t end_handle;
assert(ots && ots->service);
BT_LE_ASSERT(ots && ots->service);
assert(inc_ots_chr_feature_handle >= 2);
BT_LE_ASSERT(inc_ots_chr_feature_handle >= 2);
start_handle = inc_ots_chr_feature_handle - 2; /* server attr handle & char def handle */
end_handle = inc_ots_chr_list_cp_handle + 1; /* cccd for chr Object List Control Point */
@@ -472,7 +472,7 @@ static int gmcs_svc_check(void)
for (size_t i = 0; i < gmcs_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(gmcs_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -498,8 +498,8 @@ static int inc_ots_svc_check(void)
* the service exist in the service defined by Zephyr.
*/
assert(gatt_svc_inc_ots);
assert(ots && ots->service);
BT_LE_ASSERT(gatt_svc_inc_ots);
BT_LE_ASSERT(ots && ots->service);
LOG_DBG("[N]IncOtsSvcCheck");
@@ -512,7 +512,7 @@ static int inc_ots_svc_check(void)
for (size_t i = 0; i < ots->service->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(ots->service->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -551,7 +551,7 @@ static int inc_ots_svc_init(void)
uint8_t chr_count;
attrs = bt_ots_svc_decl_get(ots);
assert(attrs);
BT_LE_ASSERT(attrs);
chr_count = 0;
@@ -576,7 +576,7 @@ static int inc_ots_svc_init(void)
/* An additional characteristic consist of all 0s indicating end of characteristics */
svc->characteristics = bt_le_ext_calloc(INC_OTS_CHR_COUNT + 1, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - OTS Feature */
inc_ots_chr_init((void *)&svc->characteristics[0],
@@ -632,6 +632,7 @@ static int inc_ots_svc_init(void)
int bt_le_nimble_gmcs_init(bool ots_included)
{
bool inc_ots_added = false;
int rc;
LOG_DBG("[N]GmcsInit[%u]", ots_included);
@@ -642,14 +643,14 @@ int bt_le_nimble_gmcs_init(bool ots_included)
/* Extra one for terminating the included service array with NULL */
gmcs_inc_svcs = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def *));
assert(gmcs_inc_svcs);
BT_LE_ASSERT(gmcs_inc_svcs);
/* Extra one for terminating the OTS service array */
gatt_svc_inc_ots = bt_le_ext_calloc(2, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_ots);
BT_LE_ASSERT(gatt_svc_inc_ots);
ots = lib_mcs_get_ots();
assert(ots && ots->service);
BT_LE_ASSERT(ots && ots->service);
rc = inc_ots_svc_init();
if (rc) {
@@ -669,6 +670,7 @@ int bt_le_nimble_gmcs_init(bool ots_included)
LOG_ERR("[N]IncOtsAddSvcsFail[%d]", rc);
goto free;
}
inc_ots_added = true;
rc = inc_ots_svc_check();
if (rc) {
@@ -703,18 +705,23 @@ int bt_le_nimble_gmcs_init(bool ots_included)
free:
#if CONFIG_BT_OTS
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (ots_included) {
inc_ots_svc_count = 0;
free(gmcs_inc_svcs);
gmcs_inc_svcs = NULL;
if (gatt_svc_inc_ots[0].characteristics) {
free((void *)gatt_svc_inc_ots[0].characteristics);
gatt_svc_inc_ots[0].characteristics = NULL;
if (!inc_ots_added) {
if (gatt_svc_inc_ots[0].characteristics) {
free((void *)gatt_svc_inc_ots[0].characteristics);
gatt_svc_inc_ots[0].characteristics = NULL;
}
free(gatt_svc_inc_ots);
gatt_svc_inc_ots = NULL;
}
free(gatt_svc_inc_ots);
gatt_svc_inc_ots = NULL;
}
gatt_svc_gmcs[0].includes = NULL;
#endif /* CONFIG_BT_OTS */
@@ -779,7 +786,7 @@ static int mcs_svc_check(void)
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) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -798,6 +805,7 @@ static int mcs_svc_check(void)
int bt_le_nimble_mcs_init(void)
{
uint8_t count = CONFIG_BT_MCS_INSTANCE_COUNT;
bool mcs_added = false;
int rc;
/* NULL when CONFIG_BT_MCS_INSTANCE_COUNT == 0: nothing discrete to add. */
@@ -811,7 +819,7 @@ int bt_le_nimble_mcs_init(void)
/* 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 = bt_le_ext_calloc(count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_mcs);
BT_LE_ASSERT(gatt_svc_mcs);
for (int i = 0; i < count; i++) {
gatt_svc_mcs[i].type = BLE_GATT_SVC_TYPE_PRIMARY;
@@ -837,6 +845,7 @@ int bt_le_nimble_mcs_init(void)
LOG_ERR("[N]McsAddSvcsFail[%d]", rc);
goto free;
}
mcs_added = true;
rc = mcs_svc_check();
if (rc) {
@@ -846,7 +855,12 @@ int bt_le_nimble_mcs_init(void)
return 0;
free:
free(gatt_svc_mcs);
gatt_svc_mcs = NULL;
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (!mcs_added) {
free(gatt_svc_mcs);
gatt_svc_mcs = NULL;
}
return rc;
}
@@ -35,7 +35,7 @@ LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL);
#define INC_AICS_CHR_COUNT (6 + 1)
#define INC_AICS_CHR_FLAGS_STATE \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#define INC_AICS_CHR_FLAGS_GAIN \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
@@ -44,13 +44,13 @@ LOG_MODULE_REGISTER(LEA_MICS, CONFIG_BT_ISO_LOG_LEVEL);
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
#define INC_AICS_CHR_FLAGS_STATUS \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#define INC_AICS_CHR_FLAGS_CONTROL \
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC)
#define INC_AICS_CHR_FLAGS_DESCRIPTION \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
@@ -92,7 +92,7 @@ static struct ble_gatt_svc_def gatt_svc_mics[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | \
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ_ENC | \
BLE_GATT_CHR_F_WRITE_ENC,
.min_key_size = 16,
@@ -116,8 +116,8 @@ static int inc_aics_svc_check(void)
* the service exist in the service defined by Zephyr.
*/
assert(gatt_svc_inc_aics);
assert(inc_aics_insts);
BT_LE_ASSERT(gatt_svc_inc_aics);
BT_LE_ASSERT(inc_aics_insts);
LOG_DBG("[N]IncAicsSvcCheck[%u]", inc_aics_svc_count);
@@ -125,7 +125,7 @@ static int inc_aics_svc_check(void)
struct ble_gatt_svc_def *aics = &gatt_svc_inc_aics[i];
struct bt_gatt_service *svc = inc_aics_insts[i].svc_p;
assert(svc);
BT_LE_ASSERT(svc);
for (const struct ble_gatt_chr_def *chr = aics->characteristics;
chr && chr->uuid; chr++) {
@@ -136,7 +136,7 @@ static int inc_aics_svc_check(void)
for (size_t j = 0; j < svc->attr_count; j++) {
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -176,7 +176,7 @@ static int mics_svc_check(void)
for (size_t i = 0; i < mics_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(mics_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -198,14 +198,14 @@ static int inc_aics_attr_handle_set(void)
uint16_t start_handle;
uint16_t end_handle;
assert(inc_aics_insts);
BT_LE_ASSERT(inc_aics_insts);
LOG_DBG("[N]IncAicsAttrHdlSet[%u]", inc_aics_svc_count);
for (size_t i = 0; i < inc_aics_svc_count; i++) {
assert(inc_aics_insts[i].svc_p);
BT_LE_ASSERT(inc_aics_insts[i].svc_p);
assert(inc_aics_insts[i].state_handle >= 2);
BT_LE_ASSERT(inc_aics_insts[i].state_handle >= 2);
start_handle = inc_aics_insts[i].state_handle - 2; /* server attr handle & char def handle */
end_handle = inc_aics_insts[i].description_handle + 1; /* cccd for chr Audio Input Description */
@@ -307,7 +307,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
svc->includes = NULL;
svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - Audio Input State */
inc_aics_chr_init((void *)&svc->characteristics[0],
@@ -349,6 +349,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
int bt_le_nimble_mics_init(void *micp_inc)
{
struct bt_micp_included *micp_included;
bool inc_aics_added = false;
uint8_t inc_count;
int rc;
@@ -371,16 +372,16 @@ int bt_le_nimble_mics_init(void *micp_inc)
inc_count = inc_aics_svc_count + 1;
mics_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
assert(mics_inc_svcs);
BT_LE_ASSERT(mics_inc_svcs);
/* MICS may include zero or more instances of AICS */
if (inc_aics_svc_count) {
inc_aics_insts = bt_le_ext_calloc(inc_aics_svc_count, sizeof(struct inc_aics_inst));
assert(inc_aics_insts);
BT_LE_ASSERT(inc_aics_insts);
/* Extra one for terminating the AICS service array */
gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_aics);
BT_LE_ASSERT(gatt_svc_inc_aics);
for (size_t i = 0; i < inc_aics_svc_count; i++) {
inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]);
@@ -406,6 +407,7 @@ int bt_le_nimble_mics_init(void *micp_inc)
LOG_ERR("[N]IncAicsAddSvcsFail[%d]", rc);
goto free;
}
inc_aics_added = true;
rc = inc_aics_svc_check();
if (rc) {
@@ -439,22 +441,28 @@ int bt_le_nimble_mics_init(void *micp_inc)
return 0;
free:
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. That covers
* inc_aics_insts too: the registered chr_defs point at its handle fields. */
if (micp_included) {
free(mics_inc_svcs);
mics_inc_svcs = NULL;
if (inc_aics_svc_count) {
free(inc_aics_insts);
inc_aics_insts = NULL;
if (!inc_aics_added) {
free(inc_aics_insts);
inc_aics_insts = NULL;
for (size_t i = 0; i < inc_aics_svc_count; i++) {
free((void *)gatt_svc_inc_aics[i].characteristics);
gatt_svc_inc_aics[i].characteristics = NULL;
for (size_t i = 0; i < inc_aics_svc_count; i++) {
free((void *)gatt_svc_inc_aics[i].characteristics);
gatt_svc_inc_aics[i].characteristics = NULL;
}
free(gatt_svc_inc_aics);
gatt_svc_inc_aics = NULL;
}
free(gatt_svc_inc_aics);
gatt_svc_inc_aics = NULL;
inc_aics_svc_count = 0;
}
}
@@ -62,7 +62,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_PAC_SNK_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
#else /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */
@@ -77,13 +77,13 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_WRITE_ENC,
#elif CONFIG_BT_PAC_SNK_LOC_WRITEABLE && !CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
#elif !CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
#else /* !CONFIG_BT_PAC_SNK_LOC_WRITEABLE && !CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
#endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE && CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */
@@ -99,7 +99,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_PAC_SRC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
#else /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */
@@ -114,13 +114,13 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_WRITE_ENC,
#elif CONFIG_BT_PAC_SRC_LOC_WRITEABLE && !CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC,
#elif !CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
#else /* !CONFIG_BT_PAC_SRC_LOC_WRITEABLE && !CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
#endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE && CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */
@@ -134,7 +134,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
.val_handle = &pacs_ava_ctx_handle,
}, {
@@ -143,7 +143,7 @@ static const struct ble_gatt_svc_def gatt_svc_pacs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
#else /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */
@@ -173,13 +173,13 @@ int bt_le_nimble_pacs_attr_handle_set(void)
}
#if CONFIG_BT_PAC_SNK
assert(pacs_snk_handle >= 2);
BT_LE_ASSERT(pacs_snk_handle >= 2);
start_handle = pacs_snk_handle - 2; /* server attr handle & char def handle */
#elif CONFIG_BT_PAC_SRC
assert(pacs_src_handle >= 2);
BT_LE_ASSERT(pacs_src_handle >= 2);
start_handle = pacs_src_handle - 2; /* server attr handle & char def handle */
#else
assert(pacs_ava_ctx_handle >= 2);
BT_LE_ASSERT(pacs_ava_ctx_handle >= 2);
start_handle = pacs_ava_ctx_handle - 2; /* server attr handle & char def handle */
#endif
#if CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE
@@ -234,7 +234,7 @@ static int pacs_svc_check(void)
for (size_t i = 0; i < pacs_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(pacs_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -42,7 +42,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_UCI_VAL),
@@ -56,21 +56,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_URI_LIST_VAL),
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_SIGNAL_STRENGTH_VAL),
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_SIGNAL_INTERVAL_VAL),
@@ -86,7 +86,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_CCID_VAL),
@@ -100,21 +100,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_INCOMING_URI_VAL),
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_CALL_STATE_VAL),
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_CALL_CONTROL_POINT_VAL),
@@ -122,7 +122,7 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE | \
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC,
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_OPTIONAL_OPCODES_VAL),
@@ -136,21 +136,21 @@ static const struct ble_gatt_svc_def gatt_svc_gtbs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_NOTIFY,
.flags = BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_INCOMING_CALL_VAL),
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
.uuid = BLE_UUID16_DECLARE(BT_UUID_TBS_FRIENDLY_NAME_VAL),
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
0, /* No more characteristics in this service. */
@@ -233,7 +233,7 @@ static int gtbs_svc_check(void)
for (size_t i = 0; i < gtbs_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(gtbs_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -328,7 +328,7 @@ static int tbs_svc_check(void)
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) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -112,7 +112,7 @@ static int tmas_svc_check(void)
for (size_t i = 0; i < tmas_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(tmas_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -37,17 +37,17 @@ LOG_MODULE_REGISTER(LEA_VCS, CONFIG_BT_ISO_LOG_LEVEL);
#define INC_VOCS_CHR_COUNT (4 + 1)
#define INC_VOCS_CHR_FLAGS_STATE \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#define INC_VOCS_CHR_FLAGS_LOCATION \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
#define INC_VOCS_CHR_FLAGS_CONTROL \
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC)
#define INC_VOCS_CHR_FLAGS_DESCRIPTION \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_vocs_svc_count;
@@ -73,7 +73,7 @@ static const ble_uuid16_t inc_vocs_uuid_description = BLE_UUID16_INIT(BT_UUID_VO
#define INC_AICS_CHR_COUNT (6 + 1)
#define INC_AICS_CHR_FLAGS_STATE \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#define INC_AICS_CHR_FLAGS_GAIN \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
@@ -82,13 +82,13 @@ static const ble_uuid16_t inc_vocs_uuid_description = BLE_UUID16_INIT(BT_UUID_VO
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC)
#define INC_AICS_CHR_FLAGS_STATUS \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC)
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC)
#define INC_AICS_CHR_FLAGS_CONTROL \
(BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC)
#define INC_AICS_CHR_FLAGS_DESCRIPTION \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | \
(BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC)
static BT_AUDIO_EXT_RAM_BSS_ATTR uint8_t inc_aics_svc_count;
@@ -129,7 +129,7 @@ static struct ble_gatt_svc_def gatt_svc_vcs[] = {
.access_cb = bt_le_nimble_gatts_access_cb_safe,
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
.min_key_size = 16,
}, {
/* Volume Control Service -- Volume COntrol Point characteristic */
@@ -146,7 +146,7 @@ static struct ble_gatt_svc_def gatt_svc_vcs[] = {
.arg = NULL,
.descriptors = NULL, /* NULL if no descriptors. Do not include CCCD */
#if CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC,
#else /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
#endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */
@@ -170,7 +170,7 @@ static int inc_vocs_svc_check(void)
* the service exist in the service defined by Zephyr.
*/
assert(gatt_svc_inc_vocs);
BT_LE_ASSERT(gatt_svc_inc_vocs);
LOG_DBG("[N]IncVocsSvcCheck[%u]", inc_vocs_svc_count);
@@ -178,7 +178,7 @@ static int inc_vocs_svc_check(void)
struct ble_gatt_svc_def *vocs = &gatt_svc_inc_vocs[i];
struct bt_gatt_service *svc = inc_vocs_insts[i].svc_p;
assert(svc);
BT_LE_ASSERT(svc);
for (const struct ble_gatt_chr_def *chr = vocs->characteristics;
chr && chr->uuid; chr++) {
@@ -189,7 +189,7 @@ static int inc_vocs_svc_check(void)
for (size_t j = 0; j < svc->attr_count; j++) {
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -215,7 +215,7 @@ static int inc_aics_svc_check(void)
* the service exist in the service defined by Zephyr.
*/
assert(gatt_svc_inc_aics);
BT_LE_ASSERT(gatt_svc_inc_aics);
LOG_DBG("[N]IncAicsSvcCheck[%u]", inc_aics_svc_count);
@@ -223,7 +223,7 @@ static int inc_aics_svc_check(void)
struct ble_gatt_svc_def *aics = &gatt_svc_inc_aics[i];
struct bt_gatt_service *svc = inc_aics_insts[i].svc_p;
assert(svc);
BT_LE_ASSERT(svc);
for (const struct ble_gatt_chr_def *chr = aics->characteristics;
chr && chr->uuid; chr++) {
@@ -234,7 +234,7 @@ static int inc_aics_svc_check(void)
for (size_t j = 0; j < svc->attr_count; j++) {
uuid = (const struct bt_uuid_16 *)(svc->attrs + j)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -278,7 +278,7 @@ static int vcs_svc_check(void)
for (size_t i = 0; i < vcs_svc->attr_count; i++) {
uuid = (const struct bt_uuid_16 *)(vcs_svc->attrs + i)->uuid;
if (uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
if (uuid && uuid->uuid.type == BT_LE_NIMBLE_GATT_UUID_TO_Z(check->u.type) &&
uuid->val == check->value) {
chr_found = true;
break;
@@ -303,9 +303,9 @@ static int inc_vocs_attr_handle_set(void)
LOG_DBG("[N]IncVocsAttrHdlSet[%u]", inc_vocs_svc_count);
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
assert(inc_vocs_insts[i].svc_p);
BT_LE_ASSERT(inc_vocs_insts[i].svc_p);
assert(inc_vocs_insts[i].state_handle >= 2);
BT_LE_ASSERT(inc_vocs_insts[i].state_handle >= 2);
start_handle = inc_vocs_insts[i].state_handle - 2; /* server attr handle & char def handle */
end_handle = inc_vocs_insts[i].description_handle + 1; /* cccd for chr Audio Output Description */
@@ -339,9 +339,9 @@ static int inc_aics_attr_handle_set(void)
LOG_DBG("[N]IncAicsAttrHdlSet[%u]", inc_aics_svc_count);
for (size_t i = 0; i < inc_aics_svc_count; i++) {
assert(inc_aics_insts[i].svc_p);
BT_LE_ASSERT(inc_aics_insts[i].svc_p);
assert(inc_aics_insts[i].state_handle >= 2);
BT_LE_ASSERT(inc_aics_insts[i].state_handle >= 2);
start_handle = inc_aics_insts[i].state_handle - 2; /* server attr handle & char def handle */
end_handle = inc_aics_insts[i].description_handle + 1; /* cccd for chr Audio Input Description */
@@ -450,7 +450,7 @@ static void inc_vocs_svc_init(struct inc_vocs_inst *inst,
svc->includes = NULL;
svc->characteristics = bt_le_ext_calloc(INC_VOCS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - Volume Offset State */
inc_vocs_chr_init((void *)&svc->characteristics[0],
@@ -503,7 +503,7 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
svc->includes = NULL;
svc->characteristics = bt_le_ext_calloc(INC_AICS_CHR_COUNT, sizeof(struct ble_gatt_chr_def));
assert(svc->characteristics);
BT_LE_ASSERT(svc->characteristics);
/* Characteristic - Audio Input State */
inc_aics_chr_init((void *)&svc->characteristics[0],
@@ -545,6 +545,8 @@ static void inc_aics_svc_init(struct inc_aics_inst *inst,
int bt_le_nimble_vcs_init(void *vcp_inc)
{
struct bt_vcp_included *vcp_included;
bool inc_vocs_added = false;
bool inc_aics_added = false;
uint8_t inc_count;
int rc;
@@ -571,13 +573,13 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
inc_count = inc_vocs_svc_count + inc_aics_svc_count + 1;
vcs_inc_svcs = bt_le_ext_calloc(inc_count, sizeof(struct ble_gatt_svc_def *));
assert(vcs_inc_svcs);
BT_LE_ASSERT(vcs_inc_svcs);
/* VCS may include zero or more instances of VOCS */
if (inc_vocs_svc_count) {
/* Extra one for terminating the VOCS service array */
gatt_svc_inc_vocs = bt_le_ext_calloc(inc_vocs_svc_count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_vocs);
BT_LE_ASSERT(gatt_svc_inc_vocs);
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
inc_vocs_svc_init(&inc_vocs_insts[i], &gatt_svc_inc_vocs[i]);
@@ -603,6 +605,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
LOG_ERR("[N]IncVocsAddSvcsFail[%d]", rc);
goto free;
}
inc_vocs_added = true;
rc = inc_vocs_svc_check();
if (rc) {
@@ -614,7 +617,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
if (inc_aics_svc_count) {
/* Extra one for terminating the AICS service array */
gatt_svc_inc_aics = bt_le_ext_calloc(inc_aics_svc_count + 1, sizeof(struct ble_gatt_svc_def));
assert(gatt_svc_inc_aics);
BT_LE_ASSERT(gatt_svc_inc_aics);
for (size_t i = 0; i < inc_aics_svc_count; i++) {
inc_aics_svc_init(&inc_aics_insts[i], &gatt_svc_inc_aics[i]);
@@ -640,6 +643,7 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
LOG_ERR("[N]IncAicsAddSvcsFail[%d]", rc);
goto free;
}
inc_aics_added = true;
rc = inc_aics_svc_check();
if (rc) {
@@ -673,15 +677,20 @@ int bt_le_nimble_vcs_init(void *vcp_inc)
return 0;
free:
/* Once ble_gatts_add_svcs() succeeds NimBLE keeps the svc_def pointer and
* offers no per-service unregister, so an added service must be leaked
* rather than freed into a dangling entry of its global list. */
if (vcp_included) {
if (inc_vocs_svc_count) {
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
free((void *)gatt_svc_inc_vocs[i].characteristics);
gatt_svc_inc_vocs[i].characteristics = NULL;
}
if (!inc_vocs_added) {
for (size_t i = 0; i < inc_vocs_svc_count; i++) {
free((void *)gatt_svc_inc_vocs[i].characteristics);
gatt_svc_inc_vocs[i].characteristics = NULL;
}
free(gatt_svc_inc_vocs);
gatt_svc_inc_vocs = NULL;
free(gatt_svc_inc_vocs);
gatt_svc_inc_vocs = NULL;
}
inc_vocs_svc_count = 0;
}
@@ -689,7 +698,7 @@ free:
if (inc_aics_svc_count) {
/* A VOCS-phase failure reaches here with the count already set
* but gatt_svc_inc_aics not yet allocated (still NULL). */
if (gatt_svc_inc_aics) {
if (!inc_aics_added && gatt_svc_inc_aics) {
for (size_t i = 0; i < inc_aics_svc_count; i++) {
free((void *)gatt_svc_inc_aics[i].characteristics);
gatt_svc_inc_aics[i].characteristics = NULL;
@@ -7,7 +7,6 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/autoconf.h>
#include <zephyr/logging/log.h>
@@ -27,6 +26,24 @@
LOG_MODULE_REGISTER(LEA_GSRV, CONFIG_BT_ISO_LOG_LEVEL);
/* NimBLE access callbacks must return 0 or a positive ATT error (stored as
* uint8_t). Zephyr GATT handlers return BT_GATT_ERR(att) = -att. */
static int to_nimble_att_err(int gatt_err)
{
int att;
if (gatt_err >= 0) {
return 0;
}
att = -gatt_err;
if (att > 0 && att <= UINT8_MAX) {
return att;
}
return BLE_ATT_ERR_UNLIKELY;
}
static ssize_t gatts_read_cb(void *arg, uint16_t offset, const void *data, uint16_t len)
{
struct os_mbuf *om;
@@ -35,20 +52,21 @@ static ssize_t gatts_read_cb(void *arg, uint16_t offset, const void *data, uint1
ARG_UNUSED(offset);
om = (struct os_mbuf *)arg;
assert(om);
BT_LE_ASSERT(om);
LOG_DBG("[N]GattsRdCb[%u][%u]", offset, len);
if (data == NULL || len == 0) {
rc = 0;
} else {
rc = os_mbuf_append(om, data, len);
if (rc) {
LOG_ERR("[N]MbufAppendFail[%d]", rc);
}
return 0;
}
return (rc == 0 ? len : 0);
rc = os_mbuf_append(om, data, len);
if (rc) {
LOG_ERR("[N]MbufAppendFail[%d]", rc);
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
return len;
}
static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
@@ -57,17 +75,17 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
struct bt_le_nimble_gatt_read_cb cb;
const struct bt_gatt_attr *attr;
struct bt_conn *conn;
uint8_t *data;
uint8_t *data = NULL;
ssize_t rc;
assert(ctx);
BT_LE_ASSERT(ctx);
LOG_DBG("[N]GattsAccessCb[%u][%u][%02x]", conn_handle, attr_handle, ctx->op);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("[N]NotConn[%d]", __LINE__);
return -ENOTCONN;
return BLE_ATT_ERR_UNLIKELY;
}
switch (ctx->op) {
@@ -75,12 +93,12 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
attr = bt_gatts_find_attr_by_handle(attr_handle);
if (attr == NULL) {
LOG_WRN("[N]RdAttrNotFound[%u]", attr_handle);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE);
return BLE_ATT_ERR_INVALID_HANDLE;
}
if (attr->read == NULL) {
LOG_WRN("[N]RdNotPermit");
return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED);
return BLE_ATT_ERR_READ_NOT_PERMITTED;
}
cb.read_cb = gatts_read_cb;
@@ -89,7 +107,7 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
rc = attr->read(conn, attr, (void *)&cb, UINT16_MAX, 0);
if (rc < 0) {
LOG_WRN("[N]RdGattErr[%u][%d]", attr_handle, rc);
return BT_GATT_ERR(rc);
return to_nimble_att_err(rc);
}
return 0;
@@ -98,12 +116,12 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
attr = bt_gatts_find_attr_by_handle(attr_handle);
if (attr == NULL) {
LOG_WRN("[N]WrAttrNotFound[%u]", attr_handle);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE);
return BLE_ATT_ERR_INVALID_HANDLE;
}
if (attr->write == NULL) {
LOG_WRN("[N]WrNotPermit");
return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED);
return BLE_ATT_ERR_WRITE_NOT_PERMITTED;
}
if (BT_UUID_16(attr->uuid)->val == BT_UUID_BASS_CONTROL_POINT_VAL) {
@@ -120,14 +138,14 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
if (OS_MBUF_PKTLEN(ctx->om) > alloc_len) {
LOG_WRN("[N]WrBassCtrlPtTooLong[%u > %u]",
OS_MBUF_PKTLEN(ctx->om), alloc_len);
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
data = bt_le_ext_calloc(1, alloc_len);
assert(data);
BT_LE_ASSERT(data);
rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data);
assert(rc == 0);
BT_LE_ASSERT(rc == 0);
rc = attr->write(conn, attr, data, OS_MBUF_PKTLEN(ctx->om), 0, 0);
@@ -136,20 +154,26 @@ static int gatts_access_cb(uint16_t conn_handle, uint16_t attr_handle,
} else {
LOG_DBG("[N]Wr[%u]", OS_MBUF_PKTLEN(ctx->om));
data = bt_le_ext_calloc(1, OS_MBUF_PKTLEN(ctx->om));
assert(data);
/* A zero-length write is a legal PDU, but calloc(0) returns NULL
* on IDF and would trip the assert; pass it on unallocated. */
if (OS_MBUF_PKTLEN(ctx->om) > 0) {
data = bt_le_ext_calloc(1, OS_MBUF_PKTLEN(ctx->om));
BT_LE_ASSERT(data);
rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data);
assert(rc == 0);
rc = os_mbuf_copydata(ctx->om, 0, OS_MBUF_PKTLEN(ctx->om), data);
BT_LE_ASSERT(rc == 0);
}
rc = attr->write(conn, attr, data, OS_MBUF_PKTLEN(ctx->om), 0, 0);
free(data);
data = NULL;
if (data != NULL) {
free(data);
data = NULL;
}
}
if (rc < 0) {
LOG_WRN("[N]WrGattErr[%u][%d]", attr_handle, rc);
return BT_GATT_ERR(rc);
return to_nimble_att_err(rc);
}
return 0;
@@ -45,6 +45,8 @@ struct bt_le_audio_start_info {
int bt_le_audio_init(void);
void bt_le_audio_deinit(void);
int bt_le_ascs_init(void);
int bt_le_bass_init(void);
+31 -22
View File
@@ -44,6 +44,7 @@
#include <../host/conn_internal.h>
#include <../host/hci_core.h>
#include "utils/assert.h"
#include "utils/mem.h"
#if CONFIG_BT_BLUEDROID_ENABLED
@@ -52,6 +53,10 @@
#include "nimble/init.h"
#endif
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
#include "ots/adapter/l2cap.h"
#endif
#include "../../../lib/include/audio.h"
#include "esp_ble_audio_common_api.h"
@@ -180,7 +185,7 @@ static const uint16_t ext_structs[] = {
sizeof(struct bt_bond_info),
};
#define LEA_VERSION (0x20260724)
#define LEA_VERSION (0x20260802)
struct lib_ext_cfgs {
/* BLE */
@@ -1205,29 +1210,13 @@ static void log_error(const char *format, ...)
#endif /* (CONFIG_BT_AUDIO_LOG_LEVEL >= BT_ISO_LOG_ERROR) */
}
/* Fatal assert handler registered into lib_ext_funcs._assert.
* Always logged (no LOG_LEVEL gate) — this is the last message before
* abort, and the user needs the context to diagnose.
*/
static void assert_fatal(const char *tag, size_t info,
const char *file, int line, const char *func)
{
esp_log_write(ESP_LOG_ERROR, LEA_TAG,
BT_ISO_LOG_COLOR_E
"E (%lu) %s: LibAssert[%s][info=%u][%s:%d][%s]"
BT_ISO_LOG_RESET_COLOR "\n",
esp_log_timestamp(), LEA_TAG,
tag, (unsigned)info, file, line, func);
abort();
}
static const struct lib_ext_funcs ext_funcs = {
._log_dbg = (void *)log_debug,
._log_inf = (void *)log_info,
._log_wrn = (void *)log_warn,
._log_err = (void *)log_error,
._assert = (void *)assert_fatal,
._assert = (void *)bt_le_assert,
#if CONFIG_BT_AUDIO_HEAP_EXTERNAL_MEMORY
._malloc = (void *)bt_le_ext_malloc,
@@ -1319,8 +1308,6 @@ static const struct lib_ext_funcs ext_funcs = {
._conn_index = (void *)bt_conn_index,
._conn_lookup_index = (void *)bt_conn_lookup_index,
._conn_get_dst = (void *)bt_conn_get_dst,
._conn_ref = (void *)bt_conn_ref,
._conn_unref = (void *)bt_conn_unref,
._gatt_svc_register = (void *)bt_gatt_service_register,
._gatt_svc_unregister = (void *)bt_gatt_service_unregister,
@@ -2128,10 +2115,32 @@ int bt_le_audio_init(void)
return err;
}
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
err = bt_le_l2cap_ots_init();
if (err) {
return err;
}
#endif
#if CONFIG_BT_BLUEDROID_ENABLED
return bt_le_bluedroid_audio_init();
err = bt_le_bluedroid_audio_init();
#else
return bt_le_nimble_audio_init();
err = bt_le_nimble_audio_init();
#endif
if (err) {
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_ots_deinit();
#endif
return err;
}
return 0;
}
void bt_le_audio_deinit(void)
{
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_ots_deinit();
#endif
}
@@ -7,6 +7,8 @@
config BT_OTS
bool "Object Transfer Service (OTS) [EXPERIMENTAL]"
select BT_OTS_SECONDARY_SVC
# BT_GATTS_ENABLE over BT_BLUEDROID_ENABLED: it implies the full dependency chain.
select BT_BLE_L2CAP_COC_ENABLED if BT_GATTS_ENABLE
help
Enable Object Transfer Service.
@@ -69,6 +71,8 @@ endif # BT_OTS
config BT_OTS_CLIENT
bool "Object Transfer Service Client [Experimental]"
# BT_GATTC_ENABLE over BT_BLUEDROID_ENABLED: it implies the full dependency chain.
select BT_BLE_L2CAP_COC_ENABLED if BT_GATTC_ENABLE
help
This option enables support for the Object Transfer Service Client.
@@ -0,0 +1,802 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#include <../host/conn_internal.h>
#include "osi/allocator.h"
#include "osi/thread.h"
#include "stack/l2c_api.h"
#include "stack/btm_api.h"
#include "stack/btu.h"
#include "l2c_int.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "ots/adapter/l2cap.h"
LOG_MODULE_REGISTER(ISO_B2CAP, CONFIG_BT_ISO_LOG_LEVEL);
/* BT_BLE_L2CAP_COC_MAX_CHAN sizes ble_rcb_pool[] (registered LE PSMs, not
* channels); EATT holds one slot for PSM 0x0027. */
#if CONFIG_BT_BLE_EATT_ENABLE
_Static_assert(CONFIG_BT_BLE_L2CAP_COC_MAX_CHAN >= 2,
"OTS and EATT each need an LE CoC PSM slot");
#endif /* CONFIG_BT_BLE_EATT_ENABLE */
/* L2CA callbacks identify a channel by lcid only; this table maps lcid back to
* (conn_handle, psm). One slot per connection (OTS is single-PSM). */
struct ots_chan_slot {
uint16_t conn_handle;
uint16_t lcid;
uint16_t psm;
BD_ADDR peer_addr;
bool used;
bool pending; /* connect request sent, awaiting cfm */
bool tx_pending; /* SDU in flight, awaiting decongestion */
};
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ots_chan_slot ots_chans[L2CAP_OTS_MAX_CHAN];
static BT_AUDIO_EXT_RAM_BSS_ATTR uint16_t ots_reg_psm;
/* ---- BTU executor types & state -------------------------------------------
* All L2CA_* must run on BTU (single-threaded CCB). ISO entry points marshal
* via l2cap_btu_post (fire-and-forget) or l2cap_btu_invoke (sync, INIT/DEINIT
* only). ots_chans is BTU-only, so no host_lock. */
enum l2cap_btu_cmd_type {
BTU_L2CAP_CMD_INIT,
BTU_L2CAP_CMD_DEINIT,
BTU_L2CAP_CMD_CONNECT,
BTU_L2CAP_CMD_DISCONNECT,
BTU_L2CAP_CMD_SEND,
BTU_L2CAP_CMD_ACCEPT_RSP,
};
struct l2cap_btu_cmd {
enum l2cap_btu_cmd_type type;
union {
struct {
uint16_t conn_handle;
} connect;
struct {
uint16_t conn_handle;
} disconnect;
struct {
uint16_t conn_handle;
BT_HDR *p_buf;
} send;
struct {
BD_ADDR bd_addr;
uint8_t id;
uint16_t lcid;
uint16_t result;
} accept_rsp;
};
uint32_t gen; /* bumped per cycle so a late completion can be rejected */
bool sync; /* true: give the sem on completion (INIT/DEINIT only) */
};
/* Completion handoff for the sync invoke. Single set of globals, so it only works
* because INIT/DEINIT are the sole users and never overlap. */
static BT_AUDIO_CTRL_BSS_ATTR struct k_sem btu_cmd_done;
static BT_AUDIO_CTRL_BSS_ATTR bool btu_cmd_done_init;
static BT_AUDIO_CTRL_BSS_ATTR uint32_t btu_done_gen;
static BT_AUDIO_CTRL_BSS_ATTR int btu_done_ret;
static struct ots_chan_slot *ots_chan_find_used_by_lcid(uint16_t lcid)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].used && ots_chans[i].lcid == lcid) {
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_find_pending_by_addr(const BD_ADDR addr)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].pending &&
memcmp(ots_chans[i].peer_addr, addr, sizeof(BD_ADDR)) == 0) {
return &ots_chans[i];
}
}
return NULL;
}
/* psm is stamped here, not at cfm: a pending slot torn down by a link loss still
* has to post DISCONNECTED, and the upper layer keys that on the wire PSM. */
static struct ots_chan_slot *ots_chan_alloc_pending(uint16_t conn_handle, const BD_ADDR addr)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (!ots_chans[i].used && !ots_chans[i].pending) {
memset(&ots_chans[i], 0, sizeof(ots_chans[i]));
ots_chans[i].conn_handle = conn_handle;
ots_chans[i].psm = L2CAP_LE_OTS_PSM;
memcpy(ots_chans[i].peer_addr, addr, sizeof(BD_ADDR));
ots_chans[i].pending = true;
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_find_pending_by_lcid(uint16_t lcid)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].pending && ots_chans[i].lcid == lcid) {
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_alloc_used(uint16_t conn_handle, uint16_t lcid, uint16_t psm)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (!ots_chans[i].used && !ots_chans[i].pending) {
memset(&ots_chans[i], 0, sizeof(ots_chans[i]));
ots_chans[i].conn_handle = conn_handle;
ots_chans[i].lcid = lcid;
ots_chans[i].psm = psm;
ots_chans[i].used = true;
return &ots_chans[i];
}
}
return NULL;
}
static void ots_chan_free(struct ots_chan_slot *slot)
{
if (slot != NULL) {
memset(slot, 0, sizeof(*slot));
}
}
static void ots_cfg_init(tL2CAP_LE_CFG_INFO *cfg)
{
/* mps/credits left at 0: the stack substitutes its CoC Kconfig defaults. */
memset(cfg, 0, sizeof(*cfg));
cfg->mtu = L2CAP_LE_OTS_MTU;
}
/* All callbacks below run on BTU. */
#if CONFIG_BT_OTS
static void l2cap_connect_ind_cb(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id)
{
struct gatt_conn *gatt_conn;
tL2CAP_LE_CFG_INFO cfg;
int err = -ENOTCONN;
LOG_DBG("[B]L2capCocConnectInd[%04x][%04x][%u]", lcid, psm, id);
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, bd_addr, true);
if (gatt_conn != NULL) {
err = bt_le_l2cap_post_accept(gatt_conn->conn_handle, psm, lcid, id);
}
if (err) {
/* Nothing will answer on iso_task, so reject here. */
LOG_ERR("[B]L2capIndRefuse[%04x][%p][%d]", lcid, gatt_conn, err);
ots_cfg_init(&cfg);
L2CA_ConnectLECocRsp(bd_addr, id, lcid, L2CAP_LE_ERR_NO_RESOURCES, 0, &cfg);
}
}
#endif /* CONFIG_BT_OTS */
/* Unwind the optimistic chan_add done by bt_l2cap_chan_connect()/l2cap_accept():
* without a DISCONNECTED the upper layer waits for a result that never comes. */
static void l2cap_cfm_fail(uint16_t conn_handle, const BD_ADDR addr)
{
ots_chan_free(ots_chan_find_pending_by_addr(addr));
bt_le_l2cap_post_disconnected(conn_handle, L2CAP_LE_OTS_PSM);
}
/* Fires for both roles. remote_cid has no public L2CA getter, and peer_addr
* is needed to match a pending slot, so the CCB is touched for those two only. */
static void l2cap_connect_cfm_cb(UINT16 lcid, UINT16 result)
{
struct gatt_conn *gatt_conn;
struct ots_chan_slot *slot;
tL2C_CCB *p_ccb;
tL2CAP_LE_CFG_INFO peer_cfg;
uint16_t conn_handle_h, psm_h;
LOG_DBG("[B]L2capCocConnectCfm[%04x][%u]", lcid, result);
p_ccb = l2cu_find_ccb_by_cid(NULL, lcid);
if (p_ccb == NULL || p_ccb->p_lcb == NULL) {
LOG_ERR("[B]L2capCfmNoCcb[%04x][%p]", lcid, p_ccb);
return;
}
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, p_ccb->p_lcb->remote_bd_addr, true);
if (gatt_conn == NULL) {
LOG_ERR("[B]L2capCfmNoConn[%04x]", lcid);
/* No gatt_conn, so the pending slot is the only source of conn_handle. */
slot = ots_chan_find_pending_by_addr(p_ccb->p_lcb->remote_bd_addr);
if (slot != NULL) {
l2cap_cfm_fail(slot->conn_handle, p_ccb->p_lcb->remote_bd_addr);
}
return;
}
if (result != L2CAP_CONN_OK) {
LOG_ERR("[B]L2capCocConnectFail[%04x][%u]", lcid, result);
l2cap_cfm_fail(gatt_conn->conn_handle, p_ccb->p_lcb->remote_bd_addr);
return;
}
if (ots_chan_find_used_by_lcid(lcid) != NULL) {
/* Duplicate cfm for a live channel: the first one already reported it. */
LOG_ERR("[B]L2capCocChanExist[%04x]", lcid);
return;
}
if (!L2CA_GetPeerLECocConfig(lcid, &peer_cfg)) {
LOG_ERR("[B]L2capCfmNoPeerCfg[%04x]", lcid);
l2cap_cfm_fail(gatt_conn->conn_handle, p_ccb->p_lcb->remote_bd_addr);
return;
}
/* Outgoing connect: reuse the pending slot reserved at chan_connect.
* Incoming accept: no pending slot, allocate a fresh one. */
slot = ots_chan_find_pending_by_addr(p_ccb->p_lcb->remote_bd_addr);
if (slot != NULL) {
slot->pending = false;
slot->lcid = lcid;
slot->psm = L2CAP_LE_OTS_PSM;
slot->used = true;
} else {
slot = ots_chan_alloc_used(gatt_conn->conn_handle, lcid, L2CAP_LE_OTS_PSM);
if (slot == NULL) {
LOG_ERR("[B]L2capCocNoSlot[%04x]", lcid);
l2cap_cfm_fail(gatt_conn->conn_handle, p_ccb->p_lcb->remote_bd_addr);
return;
}
}
conn_handle_h = slot->conn_handle;
psm_h = slot->psm;
LOG_INF("[B]L2capCocConnect[%u][%04x][%04x][%04x][%u][%u]",
conn_handle_h, psm_h,
lcid, p_ccb->remote_cid,
L2CAP_LE_OTS_MTU, peer_cfg.mtu);
bt_le_l2cap_post_connected(conn_handle_h, psm_h,
p_ccb->remote_cid, peer_cfg.mtu,
lcid, L2CAP_LE_OTS_MTU);
}
/* Also fires for local disconnect, so DisconnectCfm needs no handler. */
static void l2cap_disconnect_ind_cb(UINT16 lcid, BOOLEAN local_init)
{
struct ots_chan_slot *slot;
uint16_t conn_handle_h, psm_h;
LOG_DBG("[B]L2capCocDisconnectInd[%04x][%u]", lcid, local_init);
slot = ots_chan_find_used_by_lcid(lcid);
if (slot == NULL) {
/* A link lost while the connect was still outstanding arrives here, not
* as a failed cfm (see the LP_DISCONNECT_IND case of l2c_csm's
* CST_W4_L2CAP_CONNECT_RSP). Without this the pending slot would leak and
* block every later OTS connect on that conn_handle. */
slot = ots_chan_find_pending_by_lcid(lcid);
if (slot == NULL) {
LOG_ERR("[B]L2capDisconnectInvCocChan[%04x]", lcid);
return;
}
}
conn_handle_h = slot->conn_handle;
psm_h = slot->psm;
ots_chan_free(slot);
LOG_INF("[B]L2capCocDisconnect[%u][%04x]", conn_handle_h, psm_h);
bt_le_l2cap_post_disconnected(conn_handle_h, psm_h);
}
static void l2cap_data_ind_cb(UINT16 lcid, BT_HDR *p_buf)
{
struct ots_chan_slot *slot;
uint16_t conn_handle_h, psm_h;
if (p_buf == NULL) {
LOG_ERR("[B]L2capRecvNullBuf[%04x]", lcid);
return;
}
LOG_DBG("[B]L2capCocRecv[%04x][%u]", lcid, p_buf->len);
slot = ots_chan_find_used_by_lcid(lcid);
if (slot == NULL) {
LOG_ERR("[B]L2capRecvOnInvCocChan[%04x]", lcid);
osi_free(p_buf);
return;
}
conn_handle_h = slot->conn_handle;
psm_h = slot->psm;
bt_le_l2cap_post_received(conn_handle_h, psm_h,
p_buf->data + p_buf->offset, p_buf->len);
/* SDU ownership was handed to this callback; the stack osi_malloc'd it. */
osi_free(p_buf);
}
/* Decongestion doubles as TX-done but can fire twice per SDU; tx_pending
* collapses the pair into one ops->sent. */
static void l2cap_congestion_cb(UINT16 lcid, BOOLEAN congested)
{
struct ots_chan_slot *slot;
uint16_t conn_handle_h, psm_h;
LOG_DBG("[B]L2capCocCongestion[%04x][%u]", lcid, congested);
slot = ots_chan_find_used_by_lcid(lcid);
if (slot == NULL) {
LOG_ERR("[B]L2capCongestionInvCocChan[%04x]", lcid);
return;
}
if (congested || !slot->tx_pending) {
return;
}
slot->tx_pending = false;
conn_handle_h = slot->conn_handle;
psm_h = slot->psm;
bt_le_l2cap_post_sent(conn_handle_h, psm_h);
}
/* ---- BTU executor functions ---------------------------------------------- */
static int l2cap_btu_do_accept_rsp(struct l2cap_btu_cmd *cmd)
{
tL2CAP_LE_CFG_INFO cfg;
ots_cfg_init(&cfg);
if (!L2CA_ConnectLECocRsp(cmd->accept_rsp.bd_addr, cmd->accept_rsp.id,
cmd->accept_rsp.lcid, cmd->accept_rsp.result, 0, &cfg)) {
LOG_ERR("[B]L2capAcceptFail[%04x]", cmd->accept_rsp.lcid);
return -EIO;
}
return 0;
}
static void l2cap_btu_exec(void *ctx)
{
struct l2cap_btu_cmd *cmd = ctx;
tL2CAP_LE_CFG_INFO cfg;
uint16_t lcid;
int ret = -EINVAL;
switch (cmd->type) {
case BTU_L2CAP_CMD_INIT: {
tL2CAP_APPL_INFO appl = {0};
#if CONFIG_BT_OTS
appl.pL2CA_ConnectInd_Cb = l2cap_connect_ind_cb;
#endif /* CONFIG_BT_OTS */
appl.pL2CA_ConnectCfm_Cb = l2cap_connect_cfm_cb;
appl.pL2CA_DisconnectInd_Cb = l2cap_disconnect_ind_cb;
appl.pL2CA_DataInd_Cb = l2cap_data_ind_cb;
appl.pL2CA_CongestionStatus_Cb = l2cap_congestion_cb;
ots_reg_psm = L2CA_RegisterLECoc(L2CAP_LE_OTS_PSM, &appl);
if (ots_reg_psm == 0) {
ret = -EIO;
break;
}
BTM_SetSecurityLevel(TRUE, "BLE_L2CAP_OTS", BTM_SEC_SERVICE_GEN_NET,
BTM_SEC_NONE, ots_reg_psm, BTM_SEC_PROTO_L2CAP, 0);
BTM_SetSecurityLevel(FALSE, "BLE_L2CAP_OTS", BTM_SEC_SERVICE_GEN_NET,
BTM_SEC_NONE, ots_reg_psm, BTM_SEC_PROTO_L2CAP, 0);
ret = 0;
break;
}
case BTU_L2CAP_CMD_DEINIT:
/* Tear our channels down first: L2CA_DeregisterLECoc only inspects each
* link's first CCB, so a CoC CCB sitting behind another one survives and
* is left pointing at the released RCB (p_ccb->p_rcb->api use-after-free). */
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
/* pending too: its CCB is live from ConnectLECocReq onwards, and
LECocDisconnect cancels a not-yet-open channel locally. */
if (ots_chans[i].used || ots_chans[i].pending) {
L2CA_LECocDisconnect(ots_chans[i].lcid);
}
}
if (ots_reg_psm) {
L2CA_DeregisterLECoc(ots_reg_psm);
ots_reg_psm = 0;
}
ret = 0;
break;
case BTU_L2CAP_CMD_CONNECT: {
struct gatt_conn *gatt_conn;
struct ots_chan_slot *slot;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(cmd->connect.conn_handle);
if (gatt_conn == NULL) {
LOG_ERR("[B]L2capNoConnInfo[%u]", cmd->connect.conn_handle);
ret = -ENOTCONN;
break;
}
/* Reject if this connection already has an OTS channel. */
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if ((ots_chans[i].used || ots_chans[i].pending) &&
ots_chans[i].conn_handle == cmd->connect.conn_handle) {
LOG_WRN("[B]L2capOtsChanExist[%u]", cmd->connect.conn_handle);
ret = -EALREADY;
break;
}
}
if (ret == -EALREADY) {
break;
}
slot = ots_chan_alloc_pending(cmd->connect.conn_handle, gatt_conn->peer.val);
if (slot == NULL) {
LOG_ERR("[B]L2capOtsNoSlot[%u]", cmd->connect.conn_handle);
ret = -ENOMEM;
break;
}
ots_cfg_init(&cfg);
/* Returns 0 on failure, LCID on success. Keep the LCID: if the link dies
* before the cfm the stack reports DisconnectInd, and by then the CCB is
* released so the LCID is the only way back to this slot. */
lcid = L2CA_ConnectLECocReq(ots_reg_psm, gatt_conn->peer.val, &cfg);
if (lcid == 0) {
ret = -EIO;
ots_chan_free(slot);
/* Post DISCONNECTED to clean up the optimistic chan_add. */
bt_le_l2cap_post_disconnected(cmd->connect.conn_handle, L2CAP_LE_OTS_PSM);
break;
}
slot->lcid = lcid;
ret = 0;
break;
}
case BTU_L2CAP_CMD_DISCONNECT: {
struct ots_chan_slot *slot = NULL;
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].used && ots_chans[i].conn_handle == cmd->disconnect.conn_handle) {
slot = &ots_chans[i];
break;
}
}
if (slot == NULL) {
LOG_WRN("[B]L2capNoOtsChan");
ret = -ENOTCONN;
break;
}
ret = L2CA_LECocDisconnect(slot->lcid) ? 0 : -EIO;
break;
}
case BTU_L2CAP_CMD_SEND: {
struct ots_chan_slot *slot = NULL;
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].used && ots_chans[i].conn_handle == cmd->send.conn_handle) {
slot = &ots_chans[i];
break;
}
}
if (slot == NULL) {
LOG_WRN("[B]L2capNoOtsChan");
/* Slot gone (disconnect won the race); free p_buf ourselves. */
osi_free(cmd->send.p_buf);
ret = -ENOTCONN;
break;
}
/* Armed before the write: a non-congested SDU completes inside it and
* raises the decongestion cb synchronously (same BTU task). */
slot->tx_pending = true;
/* On DW_FAILED the stack frees p_buf; on SUCCESS/CONGESTED it owns it. */
ret = (L2CA_LECocDataWrite(slot->lcid, cmd->send.p_buf) == L2CAP_DW_FAILED)
? -EIO : 0;
if (ret != 0) {
slot->tx_pending = false;
}
break;
}
case BTU_L2CAP_CMD_ACCEPT_RSP:
ret = l2cap_btu_do_accept_rsp(cmd);
break;
default:
ret = -EINVAL;
break;
}
if (cmd->sync) {
btu_done_ret = ret;
btu_done_gen = cmd->gen;
k_sem_give(&btu_cmd_done);
}
free(cmd);
}
/* Post cmd to BTU fire-and-forget. Returns 0 on success, -EIO if BTU is
* unavailable (cmd is freed on failure). Never blocks. No self-delivery check is
* needed - unlike the sync invoke, posting to our own queue cannot deadlock. */
static int l2cap_btu_post(struct l2cap_btu_cmd *cmd)
{
osi_thread_t *btu = btu_get_current_thread();
cmd->sync = false;
if (btu == NULL || !osi_thread_post(btu, l2cap_btu_exec, cmd, 0, 0)) {
LOG_ERR("[B]L2capBtuPostFail[%u]", cmd->type);
free(cmd);
return -EIO;
}
return 0;
}
void bt_le_bluedroid_l2cap_accept_result(uint16_t conn_handle, uint8_t l2cap_id,
uint16_t chan_handle, uint16_t result)
{
struct gatt_conn *gatt_conn;
struct l2cap_btu_cmd *cmd;
LOG_DBG("[B]L2capAcceptResult[%04x][%04x]", chan_handle, result);
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(conn_handle);
if (gatt_conn == NULL) {
LOG_ERR("[B]L2capAcceptNoConn[%u]", conn_handle);
return;
}
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capAcceptNoCmd");
return;
}
cmd->type = BTU_L2CAP_CMD_ACCEPT_RSP;
cmd->accept_rsp.id = l2cap_id;
cmd->accept_rsp.lcid = chan_handle;
cmd->accept_rsp.result = result;
memcpy(cmd->accept_rsp.bd_addr, gatt_conn->peer.val, sizeof(BD_ADDR));
l2cap_btu_post(cmd);
}
int bt_le_bluedroid_l2cap_chan_connect(uint16_t conn_handle)
{
struct l2cap_btu_cmd *cmd;
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capConnectNoCmd");
return -ENOMEM;
}
cmd->type = BTU_L2CAP_CMD_CONNECT;
cmd->connect.conn_handle = conn_handle;
/* Result comes back as CONNECTED, or DISCONNECTED if BTU's connect fails. */
return l2cap_btu_post(cmd);
}
int bt_le_bluedroid_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
{
struct l2cap_btu_cmd *cmd;
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capDisconnectNoCmd");
return -ENOMEM;
}
cmd->type = BTU_L2CAP_CMD_DISCONNECT;
cmd->disconnect.conn_handle = chan->conn->handle;
/* Result comes back as DISCONNECTED via l2cap_disconnect_ind_cb. */
return l2cap_btu_post(cmd);
}
int bt_le_bluedroid_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct l2cap_btu_cmd *cmd;
BT_HDR *p_buf;
int ret;
/* osi_malloc, not bt_le_int_malloc: the stack frees this SDU with osi_free,
* and it never reaches DMA - the TX path copies it into a K-frame buffer. */
p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + buf->len);
if (p_buf == NULL) {
LOG_ERR("[B]L2capNoBufForSend[%u]", buf->len);
return -ENOMEM;
}
/* offset stays 0: the CoC TX path builds its own K-frames, no headroom here. */
memset(p_buf, 0, sizeof(*p_buf));
p_buf->len = buf->len;
memcpy(p_buf->data, buf->data, buf->len);
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capSendNoCmd");
osi_free(p_buf);
return -ENOMEM;
}
cmd->type = BTU_L2CAP_CMD_SEND;
cmd->send.conn_handle = chan->conn->handle;
cmd->send.p_buf = p_buf;
/* Completion comes back as ops->sent; on DW_FAILED nothing does, and the
* eventual DISCONNECTED aborts the transfer. */
ret = l2cap_btu_post(cmd);
if (ret != 0) {
/* BTU unavailable: p_buf not queued, free it. buf stays with caller. */
osi_free(p_buf);
return ret;
}
/* Posted: payload is in p_buf, release caller's buf else ot_chan_tx_pool
* (1 entry) leaks. */
net_buf_unref(buf);
return 0;
}
/* Run cmd on BTU and wait — INIT/DEINIT only. Hot-path ops use
* l2cap_btu_post (fire-and-forget). cmd is freed by l2cap_btu_exec. */
static int l2cap_btu_invoke(struct l2cap_btu_cmd *cmd)
{
osi_thread_t *btu = btu_get_current_thread();
uint32_t gen = ++btu_done_gen; /* unique per cycle; stale gives are ignored */
cmd->gen = gen;
cmd->sync = true;
/* Self-delivery would deadlock: run inline and drain the give. */
if (btu != NULL && strcmp(osi_thread_name(btu), pcTaskGetName(NULL)) == 0) {
l2cap_btu_exec(cmd);
k_sem_take(&btu_cmd_done, 0);
return btu_done_ret;
}
k_sem_reset(&btu_cmd_done);
if (btu == NULL || !osi_thread_post(btu, l2cap_btu_exec, cmd, 0, 0)) {
LOG_WRN("[B]L2capBtuUnavailable[%u]", cmd->type);
l2cap_btu_exec(cmd);
k_sem_take(&btu_cmd_done, 0);
return btu_done_ret;
}
if (k_sem_take(&btu_cmd_done, K_SEM_SHORT) != 0) {
LOG_ERR("[B]L2capBtuTimeout[%u]", cmd->type);
/* cmd still on the BTU queue; bump gen so a late completion is rejected. */
btu_done_gen++;
return -ETIMEDOUT;
}
if (btu_done_gen != gen) {
LOG_ERR("[B]L2capBtuStaleGen[%u][%u]", gen, btu_done_gen);
return -ETIMEDOUT;
}
return btu_done_ret;
}
int bt_le_bluedroid_l2cap_init(void)
{
struct l2cap_btu_cmd *cmd;
int ret;
LOG_DBG("[B]L2capInit");
memset(ots_chans, 0, sizeof(ots_chans));
if (!btu_cmd_done_init) {
k_sem_create(&btu_cmd_done);
btu_cmd_done_init = true;
}
/* Drop any stale give so the first invoke doesn't match an old completion. */
k_sem_reset(&btu_cmd_done);
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capInitNoCmd");
return -ENOMEM;
}
cmd->type = BTU_L2CAP_CMD_INIT;
ret = l2cap_btu_invoke(cmd);
if (ret == -ETIMEDOUT) {
/* The command is still queued and will register the PSM once BTU drains
* it, while we report failure. Queue a DEINIT behind it (the BTU queue
* is FIFO) so the registration cannot outlive this failed init. */
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capInitNoUndoCmd");
return ret;
}
cmd->type = BTU_L2CAP_CMD_DEINIT;
l2cap_btu_post(cmd);
}
return ret;
}
void bt_le_bluedroid_l2cap_deinit(void)
{
struct l2cap_btu_cmd *cmd;
int ret;
LOG_DBG("[B]L2capDeinit");
/* If init never ran, nothing to tear down and the sem is unsafe to touch. */
if (!btu_cmd_done_init || ots_reg_psm == 0) {
memset(ots_chans, 0, sizeof(ots_chans));
return;
}
cmd = bt_le_ext_calloc(1, sizeof(*cmd));
if (cmd == NULL) {
LOG_ERR("[B]L2capDeinitNoCmd");
memset(ots_chans, 0, sizeof(ots_chans));
return;
}
cmd->type = BTU_L2CAP_CMD_DEINIT;
ret = l2cap_btu_invoke(cmd);
if (ret == 0) {
memset(ots_chans, 0, sizeof(ots_chans));
} else {
/* Timed out: DEINIT still queued. Leave ots_chans so the BTU handler's
* disconnect loop runs before PSM deregister. */
LOG_ERR("[B]L2capDeinitTimeout");
}
}
@@ -0,0 +1,829 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#include <../host/conn_internal.h>
#include "common/host.h"
#include "common/audio_attr.h"
#include "common/app/gap.h"
#include "ots/adapter/l2cap.h"
LOG_MODULE_REGISTER(ISO_L2CAP, CONFIG_BT_ISO_LOG_LEVEL);
#define L2CAP_LE_MIN_MTU 23
#define L2CAP_ECRED_MIN_MTU 64
#define L2CAP_LE_CID_DYN_START 0x0040
#define L2CAP_LE_CID_DYN_END 0x007F
#define L2CAP_LE_CID_IS_DYN(_cid) (_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END)
#define L2CAP_LE_PSM_FIXED_START 0x0001
#define L2CAP_LE_PSM_FIXED_END 0x007F
#define L2CAP_LE_PSM_DYN_START 0x0080
#define L2CAP_LE_PSM_DYN_END 0x00FF
#define L2CAP_LE_PSM_IS_DYN(_psm) (_psm >= L2CAP_LE_PSM_DYN_START && _psm <= L2CAP_LE_PSM_DYN_END)
static BT_AUDIO_EXT_RAM_BSS_ATTR sys_slist_t l2cap_servers;
/* OTS internals, declared here rather than including the OTS private headers. */
#if CONFIG_BT_OTS
extern int bt_gatt_ots_conn_cb_register(void);
extern void bt_gatt_ots_conn_cb_unregister(void);
extern int bt_gatt_ots_instances_prepare(void);
#endif /* CONFIG_BT_OTS */
#if CONFIG_BT_OTS_CLIENT
extern int bt_gatt_ots_client_conn_cb_register(void);
extern void bt_gatt_ots_client_conn_cb_unregister(void);
#endif /* CONFIG_BT_OTS_CLIENT */
extern int bt_gatt_ots_l2cap_init(void);
static struct bt_l2cap_chan *l2cap_lookup_tx_cid(struct bt_conn *conn, uint16_t cid)
{
struct bt_l2cap_chan *chan;
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) {
return chan;
}
}
return NULL;
}
__attribute__((unused))
static struct bt_l2cap_chan *l2cap_lookup_rx_cid(struct bt_conn *conn, uint16_t cid)
{
struct bt_l2cap_chan *chan;
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) {
return chan;
}
}
return NULL;
}
static struct bt_l2cap_chan *l2cap_lookup_psm(struct bt_conn *conn, uint16_t psm)
{
struct bt_l2cap_chan *chan;
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
if (BT_L2CAP_LE_CHAN(chan)->psm == psm) {
return chan;
}
}
return NULL;
}
static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
{
LOG_DBG("L2capChanAdd[%04x]", psm);
/* Attach channel to the connection */
if (sys_slist_find(&conn->channels, &chan->node, NULL)) {
LOG_WRN("L2capChanExists[%04x]", psm);
return false;
}
sys_slist_append(&conn->channels, &chan->node);
chan->conn = conn;
/* Set channel PSM */
BT_L2CAP_LE_CHAN(chan)->psm = psm;
return true;
}
static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm)
{
struct bt_l2cap_server *server = NULL;
SYS_SLIST_FOR_EACH_CONTAINER(&l2cap_servers, server, node) {
if (server->psm == psm) {
break;
}
}
return server;
}
static inline uint16_t err_to_result(int err)
{
switch (err) {
case -ENOMEM:
return L2CAP_LE_ERR_NO_RESOURCES;
case -EACCES:
return L2CAP_LE_ERR_AUTHORIZATION;
case -EPERM:
return L2CAP_LE_ERR_KEY_SIZE;
case -ENOTSUP:
/* This handle the cases where a fixed channel is registered but
* for some reason (e.g. controller not supporting a feature)
* cannot be used.
*/
return L2CAP_LE_ERR_PSM_NOT_SUPP;
default:
return L2CAP_LE_ERR_UNACCEPT_PARAMS;
}
}
static int l2cap_accept(uint16_t conn_handle, uint16_t psm,
uint16_t scid, uint16_t mtu,
uint16_t mps, uint16_t credits,
uint16_t *result)
{
struct bt_l2cap_server *server;
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
int err;
ARG_UNUSED(credits);
LOG_DBG("L2capAccept[%u][%04x][%04x][%u][%u]", conn_handle, psm, scid, mtu, mps);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
*result = L2CAP_LE_ERR_INVALID_PARAMS;
return -ENOTCONN;
}
/* Check if there is a server registered */
server = l2cap_server_lookup_psm(psm);
if (server == NULL) {
LOG_ERR("L2capSrvNotReg[%04x]", psm);
*result = L2CAP_LE_ERR_PSM_NOT_SUPP;
return -ENOTSUP;
}
if (!L2CAP_LE_CID_IS_DYN(scid)) {
LOG_ERR("L2capNotDynScid[%04x]", scid);
*result = L2CAP_LE_ERR_INVALID_SCID;
return -EINVAL;
}
chan = l2cap_lookup_tx_cid(conn, scid);
if (chan) {
LOG_WRN("L2capScidUsed[%04x]", scid);
*result = L2CAP_LE_ERR_SCID_IN_USE;
return -EALREADY;
}
/* Every event is routed back by (conn, psm), so a second channel for the
* same pair would deliver to whichever chan l2cap_lookup_psm() hits first. */
if (l2cap_lookup_psm(conn, psm) != NULL) {
LOG_WRN("L2capPsmChanExists[%u][%04x]", conn_handle, psm);
*result = L2CAP_LE_ERR_NO_RESOURCES;
return -EALREADY;
}
if (server->accept == NULL) {
LOG_ERR("L2capSrvAcceptNull");
*result = L2CAP_LE_ERR_INVALID_PARAMS;
return -EIO;
}
err = server->accept(conn, server, &chan);
if (err) {
LOG_ERR("L2capSrvAcceptFail[%d]", err);
*result = err_to_result(err);
return -EIO;
}
if (chan == NULL) {
LOG_ERR("L2capSrvAcceptNullChan");
*result = L2CAP_LE_ERR_NO_RESOURCES;
return -ENOMEM;
}
if (l2cap_chan_add(conn, chan, psm) == false) {
*result = L2CAP_LE_ERR_NO_RESOURCES;
return -ENOMEM;
}
*result = L2CAP_LE_SUCCESS;
return 0;
}
#if CONFIG_BT_BLUEDROID_ENABLED
static void l2cap_handle_accept(const struct bt_le_l2cap_event *qev)
{
uint16_t result = L2CAP_LE_ERR_NO_RESOURCES;
/* chan_handle is our local CID; the peer's is unknown until the channel is
* up, so it stands in for scid (dynamic-CID check and duplicate detection).
* mtu/mps are logging-only in l2cap_accept(). */
(void)l2cap_accept(qev->conn_handle, qev->psm, qev->accept.chan_handle,
0, 0, 0, &result);
/* Forward result, not a bool: l2cap_accept() distinguishes authentication,
* key size and SCID-in-use, and the peer is entitled to the exact reason. */
bt_le_bluedroid_l2cap_accept_result(qev->conn_handle, qev->accept.l2cap_id,
qev->accept.chan_handle, result);
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
static void l2cap_connected(uint16_t conn_handle, uint16_t psm,
uint16_t tx_cid, uint16_t tx_mtu,
uint16_t rx_cid, uint16_t rx_mtu)
{
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
LOG_DBG("L2capConnected[%u][%04x][%04x][%u][%04x][%u]",
conn_handle, psm, tx_cid, tx_mtu, rx_cid, rx_mtu);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("L2capPsmNotFound[%04x]", psm);
return;
}
BT_L2CAP_LE_CHAN(chan)->tx.cid = tx_cid;
BT_L2CAP_LE_CHAN(chan)->tx.mtu = tx_mtu;
BT_L2CAP_LE_CHAN(chan)->rx.cid = rx_cid;
BT_L2CAP_LE_CHAN(chan)->rx.mtu = rx_mtu;
if (chan->ops->connected) {
chan->ops->connected(chan);
}
}
static void l2cap_disconnected(uint16_t conn_handle, uint16_t psm)
{
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
LOG_DBG("L2capDisconnected[%u][%04x]", conn_handle, psm);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("L2capPsmNotFound[%04x]", psm);
return;
}
/* Unlink first so no later event rediscovers it by PSM. The callback must not
* free chan: it reads chan->conn, and clearing conn after returns the pool ctx. */
sys_slist_find_and_remove(&conn->channels, &chan->node);
if (chan->ops->disconnected) {
chan->ops->disconnected(chan);
}
chan->conn = NULL;
}
static void l2cap_received(uint16_t conn_handle, uint16_t psm,
uint8_t *data, uint16_t len)
{
struct bt_l2cap_chan *chan;
struct net_buf buf = {0};
struct bt_conn *conn;
LOG_DBG("L2capReceived[%u][%04x][%u]", conn_handle, psm, len);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("L2capPsmNotFound[%04x]", psm);
return;
}
buf.data = data;
buf.len = len;
if (chan->ops->recv) {
chan->ops->recv(chan, &buf);
}
}
static void l2cap_sent(uint16_t conn_handle, uint16_t psm)
{
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
LOG_DBG("L2capSent[%u][%04x]", conn_handle, psm);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capSentNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("L2capPsmNotFound[%04x]", psm);
return;
}
if (chan->ops->sent) {
chan->ops->sent(chan);
}
}
static int l2cap_post_event(struct bt_le_l2cap_event *qev)
{
int err;
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT, qev, sizeof(*qev));
if (err) {
LOG_ERR("L2capPostFail[%d][%u]", err, qev->type);
if (qev->type == BT_LE_L2CAP_EVENT_RECEIVED) {
free(qev->received.data);
}
free(qev);
}
return err;
}
/* Alloc + stamp the common header. NULL on OOM rather than abort: L2CAP is off
* the ISO data path, so losing one event beats taking the whole stack down. */
static struct bt_le_l2cap_event *l2cap_event_alloc(uint8_t type, uint16_t conn_handle,
uint16_t psm)
{
struct bt_le_l2cap_event *qev;
qev = bt_le_ext_calloc(1, sizeof(*qev));
if (qev == NULL) {
return NULL;
}
qev->type = type;
qev->conn_handle = conn_handle;
qev->psm = psm;
return qev;
}
_IDF_ONLY
int bt_le_l2cap_post_connected(uint16_t conn_handle, uint16_t psm,
uint16_t tx_cid, uint16_t tx_mtu,
uint16_t rx_cid, uint16_t rx_mtu)
{
struct bt_le_l2cap_event *qev;
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_CONNECTED, conn_handle, psm);
if (qev == NULL) {
LOG_ERR("L2capConnectedNoMem[%u][%04x]", conn_handle, psm);
return -ENOMEM;
}
qev->connected.tx_cid = tx_cid;
qev->connected.tx_mtu = tx_mtu;
qev->connected.rx_cid = rx_cid;
qev->connected.rx_mtu = rx_mtu;
return l2cap_post_event(qev);
}
_IDF_ONLY
int bt_le_l2cap_post_disconnected(uint16_t conn_handle, uint16_t psm)
{
struct bt_le_l2cap_event *qev;
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_DISCONNECTED, conn_handle, psm);
if (qev == NULL) {
LOG_ERR("L2capDisconnectedNoMem[%u][%04x]", conn_handle, psm);
return -ENOMEM;
}
return l2cap_post_event(qev);
}
_IDF_ONLY
int bt_le_l2cap_post_received(uint16_t conn_handle, uint16_t psm,
const uint8_t *data, uint16_t len)
{
struct bt_le_l2cap_event *qev;
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_RECEIVED, conn_handle, psm);
if (qev == NULL) {
LOG_ERR("L2capReceivedNoMem[%u][%04x][%u]", conn_handle, psm, len);
return -ENOMEM;
}
qev->received.len = len;
if (len) {
qev->received.data = bt_le_ext_calloc(1, len);
if (qev->received.data == NULL) {
LOG_ERR("L2capReceivedNoDataMem[%u][%04x][%u]", conn_handle, psm, len);
free(qev);
return -ENOMEM;
}
memcpy(qev->received.data, data, len);
}
return l2cap_post_event(qev);
}
_IDF_ONLY
int bt_le_l2cap_post_sent(uint16_t conn_handle, uint16_t psm)
{
struct bt_le_l2cap_event *qev;
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_SENT, conn_handle, psm);
if (qev == NULL) {
LOG_ERR("L2capSentNoMem[%u][%04x]", conn_handle, psm);
return -ENOMEM;
}
return l2cap_post_event(qev);
}
#if CONFIG_BT_BLUEDROID_ENABLED
_IDF_ONLY
int bt_le_l2cap_post_accept(uint16_t conn_handle, uint16_t psm,
uint16_t chan_handle, uint8_t l2cap_id)
{
struct bt_le_l2cap_event *qev;
qev = l2cap_event_alloc(BT_LE_L2CAP_EVENT_ACCEPT, conn_handle, psm);
if (qev == NULL) {
LOG_ERR("L2capAcceptNoMem[%u][%04x][%04x]", conn_handle, psm, chan_handle);
return -ENOMEM;
}
qev->accept.chan_handle = chan_handle;
qev->accept.l2cap_id = l2cap_id;
return l2cap_post_event(qev);
}
#else /* !CONFIG_BT_BLUEDROID_ENABLED */
_IDF_ONLY
int bt_le_l2cap_accept_safe(uint16_t conn_handle, uint16_t psm,
uint16_t scid, uint16_t mtu,
uint16_t mps, uint16_t credits,
uint16_t *result)
{
int err;
bt_le_host_lock();
err = l2cap_accept(conn_handle, psm, scid, mtu, mps, credits, result);
bt_le_host_unlock();
return err;
}
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
_IDF_ONLY
void bt_le_l2cap_handle_event(void *data, size_t data_len)
{
struct bt_le_l2cap_event *qev = data;
if (qev == NULL) {
LOG_ERR("L2capEvtNull");
return;
}
if (data_len != sizeof(*qev)) {
LOG_ERR("L2capEvtBadLen[%u]", (unsigned)data_len);
free(qev);
return;
}
bt_le_host_lock();
switch (qev->type) {
case BT_LE_L2CAP_EVENT_CONNECTED:
l2cap_connected(qev->conn_handle, qev->psm,
qev->connected.tx_cid, qev->connected.tx_mtu,
qev->connected.rx_cid, qev->connected.rx_mtu);
break;
case BT_LE_L2CAP_EVENT_DISCONNECTED:
l2cap_disconnected(qev->conn_handle, qev->psm);
break;
case BT_LE_L2CAP_EVENT_RECEIVED:
l2cap_received(qev->conn_handle, qev->psm,
qev->received.data, qev->received.len);
free(qev->received.data);
break;
case BT_LE_L2CAP_EVENT_SENT:
l2cap_sent(qev->conn_handle, qev->psm);
break;
#if CONFIG_BT_BLUEDROID_ENABLED
case BT_LE_L2CAP_EVENT_ACCEPT:
l2cap_handle_accept(qev);
break;
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
default:
LOG_ERR("L2capEvtUnknown[%u]", qev->type);
break;
}
bt_le_host_unlock();
free(qev);
}
_IDF_ONLY
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
{
int err;
LOG_DBG("L2capChanConnect[%04x]", psm);
if (chan == NULL) {
LOG_ERR("L2capChanNull");
return -EINVAL;
}
if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) {
LOG_ERR("L2capInvPsm[%04x]", psm);
return -EINVAL;
}
/* Caller holds bt_le_host_lock. chan_add is done BEFORE the adapter call
* (optimistic): the Bluedroid adapter posts fire-and-forget, so the
* CONNECTED/DISCONNECTED event needs the chan already in conn->channels.
* On failure the adapter posts DISCONNECTED, matching the Zephyr
* connect-fail contract. */
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("L2capChanNotConn[%p]", conn);
return -ENOTCONN;
}
/* Same (conn, psm) uniqueness as l2cap_accept(): event routing keys on it.
* l2cap_chan_add only rejects re-adding this very chan object. */
if (l2cap_lookup_psm(conn, psm) != NULL) {
LOG_WRN("L2capPsmChanExists[%u][%04x]", conn->handle, psm);
return -EALREADY;
}
if (!l2cap_chan_add(conn, chan, psm)) {
return -EALREADY;
}
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_l2cap_chan_connect(conn->handle);
#else
err = bt_le_nimble_l2cap_chan_connect(conn->handle);
#endif
if (err) {
/* Roll back the optimistic chan_add. */
sys_slist_find_and_remove(&conn->channels, &chan->node);
chan->conn = NULL;
return err;
}
return 0;
}
_IDF_ONLY
int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
{
int err;
LOG_DBG("L2capChanDisconnect");
if (chan == NULL) {
LOG_ERR("L2capChanNull");
return -EINVAL;
}
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
return -ENOTCONN;
}
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_l2cap_chan_disconnect(chan);
#else
err = bt_le_nimble_l2cap_chan_disconnect(chan);
#endif
if (err) {
/* If the disconnect failed, remove the channel from the connection.
* Otherwise the removal will be handled by the disconnect callback. */
if (chan->conn != NULL) {
sys_slist_find_and_remove(&chan->conn->channels, &chan->node);
chan->conn = NULL;
}
}
return err;
}
_IDF_ONLY
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
int err;
if (chan == NULL || buf == NULL) {
LOG_ERR("L2capChanBufNull[%p][%p]", chan, buf);
return -EINVAL;
}
LOG_DBG("L2capChanSend[%u]", buf->len);
/* Caller holds bt_le_host_lock. The Bluedroid adapter posts fire-and-forget;
* returns 0 (buf ownership transferred) or error (buf stays with caller).
* TX completion via ops->sent. */
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
return -ENOTCONN;
}
if (buf->len > BT_L2CAP_LE_CHAN(chan)->tx.mtu) {
LOG_ERR("L2capTooLargeBufToSend[%u][%u]", buf->len,
BT_L2CAP_LE_CHAN(chan)->tx.mtu);
return -EMSGSIZE;
}
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_l2cap_chan_send(chan, buf);
#else
err = bt_le_nimble_l2cap_chan_send(chan, buf);
#endif
return err;
}
_IDF_ONLY
int bt_l2cap_server_register(struct bt_l2cap_server *server)
{
LOG_DBG("L2capSrvReg");
if (server == NULL) {
LOG_ERR("L2capSrvNull");
return -EINVAL;
}
if (server->accept == NULL) {
LOG_ERR("L2capSrvAcceptNull");
return -EINVAL;
}
if (server->sec_level > BT_SECURITY_L4) {
LOG_ERR("L2capInvSecLevel[%u]", server->sec_level);
return -EINVAL;
}
/* Init path (no concurrency). l2cap_servers is shared with the accept
* lookup in the event handler (iso_task). */
if (server->psm) {
if (server->psm < L2CAP_LE_PSM_FIXED_START ||
server->psm > L2CAP_LE_PSM_DYN_END) {
LOG_ERR("L2capInvPsm[%04x]", server->psm);
return -EINVAL;
}
/* Check if given PSM is already in use */
if (l2cap_server_lookup_psm(server->psm)) {
LOG_WRN("L2capPsmReg");
return -EADDRINUSE;
}
LOG_DBG("L2capSrvPsm[%04x]", server->psm);
} else {
uint16_t psm;
for (psm = L2CAP_LE_PSM_DYN_START;
psm <= L2CAP_LE_PSM_DYN_END; psm++) {
if (l2cap_server_lookup_psm(psm) == NULL) {
break;
}
}
if (psm > L2CAP_LE_PSM_DYN_END) {
LOG_ERR("L2capNoFreeDynPsm");
return -EADDRNOTAVAIL;
}
LOG_DBG("L2capPsmNew[%04x]", psm);
server->psm = psm;
}
if (server->sec_level < BT_SECURITY_L1) {
server->sec_level = BT_SECURITY_L1;
}
sys_slist_append(&l2cap_servers, &server->node);
return 0;
}
int bt_le_l2cap_ots_init(void)
{
int err;
LOG_DBG("L2capOtsInit");
/* No unregister API, so a re-init would otherwise find the stale entry and
* fail bt_l2cap_server_register() with -EADDRINUSE. */
sys_slist_init(&l2cap_servers);
#if CONFIG_BT_OTS
err = bt_gatt_ots_conn_cb_register();
if (err) {
LOG_ERR("L2capOtsConnCbRegFail[%d]", err);
return err;
}
err = bt_gatt_ots_instances_prepare();
if (err) {
LOG_ERR("L2capPrepOtsInstsFail[%d]", err);
goto unreg_conn_cb;
}
#endif /* CONFIG_BT_OTS */
#if CONFIG_BT_OTS_CLIENT
err = bt_gatt_ots_client_conn_cb_register();
if (err) {
LOG_ERR("L2capOtsCliConnCbRegFail[%d]", err);
goto unreg_conn_cb;
}
#endif /* CONFIG_BT_OTS_CLIENT */
err = bt_gatt_ots_l2cap_init();
if (err) {
LOG_ERR("L2capOtsInitFail[%d]", err);
goto unreg_client_conn_cb;
}
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_l2cap_init();
#else
err = bt_le_nimble_l2cap_init();
#endif
if (err) {
LOG_ERR("L2capOtsAdapterInitFail[%d]", err);
/* bt_l2cap_server_register() has no unregister, so drop the whole list
* rather than leave the OTS PSM claimed by a half-initialised service. */
sys_slist_init(&l2cap_servers);
goto unreg_client_conn_cb;
}
return 0;
unreg_client_conn_cb:
#if CONFIG_BT_OTS_CLIENT
bt_gatt_ots_client_conn_cb_unregister();
#endif /* CONFIG_BT_OTS_CLIENT */
unreg_conn_cb:
#if CONFIG_BT_OTS
/* Leaving it registered would fail every later retry with -EEXIST. */
bt_gatt_ots_conn_cb_unregister();
#endif /* CONFIG_BT_OTS */
return err;
}
void bt_le_l2cap_ots_deinit(void)
{
LOG_DBG("L2capOtsDeinit");
/* TODO(deinit): unassign OTS obj managers / clear instance->obj_manager. */
/* Symmetric with the registers in _init: conn_cbs is never reset elsewhere. */
#if CONFIG_BT_OTS
bt_gatt_ots_conn_cb_unregister();
#endif /* CONFIG_BT_OTS */
#if CONFIG_BT_OTS_CLIENT
bt_gatt_ots_client_conn_cb_unregister();
#endif /* CONFIG_BT_OTS_CLIENT */
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_l2cap_deinit();
#else
bt_le_nimble_l2cap_deinit();
#endif
}
@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef OTS_ADAPTER_L2CAP_H_
#define OTS_ADAPTER_L2CAP_H_
#include <stdint.h>
#include "sdkconfig.h"
#include <zephyr/sys/util.h>
#if CONFIG_BT_BLUEDROID_ENABLED
#include "bluedroid/l2cap.h"
#else
#include "nimble/l2cap.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define L2CAP_LE_SUCCESS 0x0000
#define L2CAP_LE_ERR_PSM_NOT_SUPP 0x0002
#define L2CAP_LE_ERR_NO_RESOURCES 0x0004
#define L2CAP_LE_ERR_AUTHENTICATION 0x0005
#define L2CAP_LE_ERR_AUTHORIZATION 0x0006
#define L2CAP_LE_ERR_KEY_SIZE 0x0007
#define L2CAP_LE_ERR_ENCRYPTION 0x0008
#define L2CAP_LE_ERR_INVALID_SCID 0x0009
#define L2CAP_LE_ERR_SCID_IN_USE 0x000A
#define L2CAP_LE_ERR_UNACCEPT_PARAMS 0x000B
#define L2CAP_LE_ERR_INVALID_PARAMS 0x000C
#define L2CAP_LE_OTS_PSM 0x0025
#define L2CAP_LE_OTS_MTU MIN(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU, \
CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU)
#define L2CAP_OTS_MAX_CHAN CONFIG_BT_MAX_CONN
enum bt_le_l2cap_event_type {
BT_LE_L2CAP_EVENT_CONNECTED,
BT_LE_L2CAP_EVENT_DISCONNECTED,
BT_LE_L2CAP_EVENT_RECEIVED,
BT_LE_L2CAP_EVENT_SENT,
#if CONFIG_BT_BLUEDROID_ENABLED
/* Bluedroid answers an inbound connect request out-of-band via
* L2CA_ConnectLECocRsp(), so unlike NimBLE it needs no inline verdict. */
BT_LE_L2CAP_EVENT_ACCEPT,
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
};
struct bt_le_l2cap_event {
uint8_t type;
uint16_t conn_handle;
uint16_t psm;
union {
struct {
uint16_t tx_cid;
uint16_t tx_mtu;
uint16_t rx_cid;
uint16_t rx_mtu;
} connected;
struct {
uint8_t *data; /* heap copy, freed after dispatch */
uint16_t len;
} received;
#if CONFIG_BT_BLUEDROID_ENABLED
struct {
uint16_t chan_handle;
uint8_t l2cap_id;
} accept;
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
};
};
#if CONFIG_BT_BLUEDROID_ENABLED
int bt_le_l2cap_post_accept(uint16_t conn_handle, uint16_t psm,
uint16_t chan_handle, uint8_t l2cap_id);
#else /* !CONFIG_BT_BLUEDROID_ENABLED */
int bt_le_l2cap_accept_safe(uint16_t conn_handle, uint16_t psm,
uint16_t scid, uint16_t mtu,
uint16_t mps, uint16_t credits,
uint16_t *result);
#endif /* !CONFIG_BT_BLUEDROID_ENABLED */
int bt_le_l2cap_post_connected(uint16_t conn_handle, uint16_t psm,
uint16_t tx_cid, uint16_t tx_mtu,
uint16_t rx_cid, uint16_t rx_mtu);
int bt_le_l2cap_post_disconnected(uint16_t conn_handle, uint16_t psm);
int bt_le_l2cap_post_received(uint16_t conn_handle, uint16_t psm,
const uint8_t *data, uint16_t len);
/* Neither host reports a per-SDU TX completion for LE CoC, so each adapter
* synthesises it from its write's return code plus its credit-restored event. */
int bt_le_l2cap_post_sent(uint16_t conn_handle, uint16_t psm);
void bt_le_l2cap_handle_event(void *data, size_t data_len);
int bt_le_l2cap_ots_init(void);
void bt_le_l2cap_ots_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* OTS_ADAPTER_L2CAP_H_ */
@@ -0,0 +1,506 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#include <../host/conn_internal.h>
#include "host/ble_hs.h"
#include "host/ble_l2cap.h"
#include "host/ble_hs_mbuf.h"
#include "host/ble_gap.h"
#include "../../../nimble/host/src/ble_l2cap_priv.h"
#include "common/host.h"
#include "common/audio_attr.h"
#include "nimble/hs_error.h"
#include "ots/adapter/l2cap.h"
LOG_MODULE_REGISTER(ISO_N2CAP, CONFIG_BT_ISO_LOG_LEVEL);
_Static_assert(CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM && "At least one L2CAP coc shall be supported");
#define OTS_L2CAP_BUF_COUNT (3 * CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM)
#define OTS_L2CAP_MEM_SIZE OS_MEMPOOL_SIZE(OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2)
/* Staging only - both directions copy through msys mbufs, so nothing here is
* DMA-reached and PSRAM is fine. */
static BT_AUDIO_EXT_RAM_BSS_ATTR os_membuf_t ots_mem[OTS_L2CAP_MEM_SIZE];
static BT_AUDIO_EXT_RAM_BSS_ATTR struct os_mempool ots_mbuf_mempool;
static BT_AUDIO_EXT_RAM_BSS_ATTR struct os_mbuf_pool ots_mbuf_pool;
/* One slot per connection (OTS is single-PSM); pending bridges the async gap
* between ble_l2cap_connect and COC_CONNECTED.
*
* Read without host_lock on both sides: the event callback must not take it
* (NimBLE emits COC events under ble_hs_lock, so that would invert the ISO
* path's host_lock -> ble_hs_lock order). psm is cached so a stale slot never
* has to dereference chan; see chan_send for the residual window. */
struct ots_chan_slot {
struct ble_l2cap_chan *chan;
uint16_t conn_handle;
uint16_t psm;
bool used;
bool pending;
};
static BT_AUDIO_EXT_RAM_BSS_ATTR struct ots_chan_slot ots_chans[L2CAP_OTS_MAX_CHAN];
static struct ots_chan_slot *ots_chan_find_used_by_chan(struct ble_l2cap_chan *chan)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].used && ots_chans[i].chan == chan) {
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_find_used_by_conn(uint16_t conn_handle)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].used && ots_chans[i].conn_handle == conn_handle) {
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_find_pending_by_conn(uint16_t conn_handle)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (ots_chans[i].pending && ots_chans[i].conn_handle == conn_handle) {
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_alloc_pending(uint16_t conn_handle)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (!ots_chans[i].used && !ots_chans[i].pending) {
memset(&ots_chans[i], 0, sizeof(ots_chans[i]));
ots_chans[i].conn_handle = conn_handle;
ots_chans[i].pending = true;
return &ots_chans[i];
}
}
return NULL;
}
static struct ots_chan_slot *ots_chan_alloc_used(struct ble_l2cap_chan *chan, uint16_t conn_handle)
{
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if (!ots_chans[i].used && !ots_chans[i].pending) {
memset(&ots_chans[i], 0, sizeof(ots_chans[i]));
ots_chans[i].chan = chan;
ots_chans[i].conn_handle = conn_handle;
ots_chans[i].psm = chan->psm;
ots_chans[i].used = true;
return &ots_chans[i];
}
}
return NULL;
}
static void ots_chan_free(struct ots_chan_slot *slot)
{
if (slot != NULL) {
memset(slot, 0, sizeof(*slot));
}
}
static int ots_l2cap_recv_ready(struct ble_l2cap_chan *chan)
{
struct os_mbuf *sdu_rx;
int rc;
LOG_DBG("[N]L2capOtsRecvReady");
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_rx == NULL) {
LOG_ERR("[N]L2capNoBufForL2capRecv");
return -ENOMEM;
}
rc = ble_l2cap_recv_ready(chan, sdu_rx);
if (rc) {
LOG_ERR("[N]L2capRecvFail[%d]", rc);
os_mbuf_free_chain(sdu_rx);
return rc;
}
return 0;
}
static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
{
struct ble_l2cap_chan_info chan_info;
uint16_t result = 0;
size_t sdu_len;
uint16_t psm;
uint8_t *sdu;
int err;
LOG_DBG("[N]L2capOtsEvtCb[%u]", event->type);
switch (event->type) {
case BLE_L2CAP_EVENT_COC_CONNECTED: {
struct ots_chan_slot *slot;
if (event->connect.status) {
LOG_ERR("[N]L2capCocConnectFail[%d]", event->connect.status);
ots_chan_free(ots_chan_find_pending_by_conn(event->connect.conn_handle));
/* Unwind the optimistic chan_add done by bt_l2cap_chan_connect()/
* l2cap_accept(), else the upper layer waits for a result forever. */
bt_le_l2cap_post_disconnected(event->connect.conn_handle, L2CAP_LE_OTS_PSM);
return 0;
}
if (ots_chan_find_used_by_chan(event->connect.chan) != NULL) {
LOG_ERR("[N]L2capCocChanExist");
return 0;
}
/* Outgoing connect: reuse the pending slot reserved at chan_connect.
* Incoming accept: no pending slot, allocate a fresh one. */
slot = ots_chan_find_pending_by_conn(event->connect.conn_handle);
if (slot != NULL) {
slot->pending = false;
slot->chan = event->connect.chan;
slot->psm = event->connect.chan->psm;
slot->used = true;
} else {
slot = ots_chan_alloc_used(event->connect.chan, event->connect.conn_handle);
if (slot == NULL) {
LOG_ERR("[N]L2capCocNoSlot");
bt_le_l2cap_post_disconnected(event->connect.conn_handle, L2CAP_LE_OTS_PSM);
return -EIO;
}
}
if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) {
LOG_ERR("[N]L2capCocGetChanInfoFail");
/* Roll back the slot so the next COC_CONNECTED isn't refused. */
ots_chan_free(ots_chan_find_used_by_chan(event->connect.chan));
bt_le_l2cap_post_disconnected(event->connect.conn_handle, L2CAP_LE_OTS_PSM);
return -EIO;
}
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);
bt_le_l2cap_post_connected(event->connect.conn_handle, chan_info.psm,
chan_info.dcid, chan_info.peer_coc_mtu,
chan_info.scid, chan_info.our_coc_mtu);
return 0;
}
case BLE_L2CAP_EVENT_COC_DISCONNECTED: {
struct ots_chan_slot *slot;
slot = ots_chan_find_used_by_chan(event->disconnect.chan);
if (slot == NULL) {
LOG_ERR("[N]L2capDisconnectInvCocChan");
return 0;
}
psm = slot->psm;
/* Drop the slot before posting: NimBLE frees the chan once this returns,
* and the queue pairing then makes iso_task not find a dead chan. */
ots_chan_free(slot);
LOG_INF("[N]L2capCocDisconnect[%u][%04x]", event->disconnect.conn_handle, psm);
bt_le_l2cap_post_disconnected(event->disconnect.conn_handle, psm);
return 0;
}
case BLE_L2CAP_EVENT_COC_ACCEPT:
/* LE CoC allows asymmetric MTUs; peer_sdu_size is the peer's RX MTU
* (our TX ceiling) — harmless. (PTS uses 1024 > our 256; rejecting broke SCP.) */
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,
event->accept.chan->coc_tx.credits);
err = bt_le_l2cap_accept_safe(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.chan->coc_tx.credits,
&result);
if (err) {
return err;
}
ARG_UNUSED(result);
return ots_l2cap_recv_ready(event->accept.chan);
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: {
if (event->receive.sdu_rx == NULL) {
LOG_ERR("[N]L2capRecvNullSdu");
return 0;
}
LOG_DBG("[N]L2capCocRecv[%u][%04x][%u]",
event->receive.conn_handle, event->receive.chan->psm,
event->receive.sdu_rx->om_len);
sdu_len = OS_MBUF_PKTLEN(event->receive.sdu_rx);
sdu = bt_le_ext_calloc(1, sdu_len);
if (sdu == NULL) {
LOG_ERR("[N]L2capRecvNoMem[%u]", (unsigned)sdu_len);
/* Drop the SDU but keep the channel usable: hand a fresh RX buffer
* back so the peer can retry rather than stalling on credits. */
os_mbuf_free_chain(event->receive.sdu_rx);
return ots_l2cap_recv_ready(event->receive.chan);
}
err = os_mbuf_copydata(event->receive.sdu_rx, 0, sdu_len, sdu);
if (err) {
LOG_ERR("[N]L2capRecvCopyFail[%d]", err);
} else {
bt_le_l2cap_post_received(event->receive.conn_handle,
event->receive.chan->psm, sdu, sdu_len);
}
os_mbuf_free_chain(event->receive.sdu_rx);
free(sdu);
return ots_l2cap_recv_ready(event->receive.chan);
}
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: {
LOG_DBG("[N]L2capCocTxUnstalled[%u][%d]",
event->tx_unstalled.conn_handle, event->tx_unstalled.status);
/* Report completion even on failure: ops->sent carries no status, and
* silence would leave the upper layer's TX pending forever. */
if (event->tx_unstalled.status) {
LOG_ERR("[N]L2capCocTxFail[%d]", event->tx_unstalled.status);
}
bt_le_l2cap_post_sent(event->tx_unstalled.conn_handle,
event->tx_unstalled.chan->psm);
return 0;
}
default:
return 0;
}
}
int bt_le_nimble_l2cap_chan_connect(uint16_t conn_handle)
{
struct os_mbuf *sdu_rx;
struct ots_chan_slot *slot;
int rc;
/* Reject if this connection already has an OTS channel. ots_chans is a
* cache under ble_hs_lock, so no host_lock here. */
for (int i = 0; i < L2CAP_OTS_MAX_CHAN; i++) {
if ((ots_chans[i].used || ots_chans[i].pending) &&
ots_chans[i].conn_handle == conn_handle) {
LOG_WRN("[N]L2capOtsChanExist[%u]", conn_handle);
return -EALREADY;
}
}
slot = ots_chan_alloc_pending(conn_handle);
if (slot == NULL) {
LOG_ERR("[N]L2capOtsNoSlot[%u]", conn_handle);
return -ENOMEM;
}
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_rx == NULL) {
ots_chan_free(slot);
LOG_ERR("[N]L2capNoBufForConnect");
return -ENOMEM;
}
rc = ble_l2cap_connect(conn_handle, L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU,
sdu_rx, ots_l2cap_event_cb, NULL);
if (rc) {
/* NimBLE takes ownership of sdu_rx and frees it on every failure path */
ots_chan_free(slot);
LOG_ERR("[N]L2capConnectFail[%d]", rc);
return nimble_err_to_errno(rc);
}
return 0;
}
int bt_le_nimble_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
{
struct ots_chan_slot *slot;
struct ble_l2cap_chan *ble_chan = NULL;
uint16_t conn_handle_h;
struct ble_gap_conn_desc desc;
int rc;
slot = ots_chan_find_used_by_conn(chan->conn->handle);
if (slot == NULL) {
LOG_WRN("[N]L2capNoOtsChan");
return -ENOTCONN;
}
ble_chan = slot->chan;
conn_handle_h = slot->conn_handle;
/* Best-effort liveness check: narrows the window where ble_chan could be
* freed between this lookup and ble_l2cap_disconnect acquiring ble_hs_lock. */
if (ble_gap_conn_find(conn_handle_h, &desc) != 0) {
LOG_WRN("[N]L2capDiscConnGone[%u]", conn_handle_h);
return -ENOTCONN;
}
rc = ble_l2cap_disconnect(ble_chan);
if (rc) {
LOG_ERR("[N]L2capDisconnectFail[%d]", rc);
return nimble_err_to_errno(rc);
}
return 0;
}
int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct ots_chan_slot *slot;
struct ble_l2cap_chan *ble_chan = NULL;
uint16_t conn_handle_h, psm_h;
struct os_mbuf *sdu_tx;
struct ble_gap_conn_desc desc;
int rc;
slot = ots_chan_find_used_by_conn(chan->conn->handle);
if (slot == NULL) {
LOG_WRN("[N]L2capNoOtsChan");
return -ENOTCONN;
}
ble_chan = slot->chan;
conn_handle_h = slot->conn_handle;
psm_h = slot->psm;
/* Best-effort liveness check: ble_gap_conn_find takes ble_hs_lock, so the
* conn can't be torn down while it runs. It narrows but does not close the
* window before ble_l2cap_send — that reads chan->coc_tx.mtu before taking
* ble_hs_lock, so a concurrently freed chan is still dereferenced there. */
if (ble_gap_conn_find(conn_handle_h, &desc) != 0) {
LOG_WRN("[N]L2capSendConnGone[%u]", conn_handle_h);
return -ENOTCONN;
}
sdu_tx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_tx == NULL) {
LOG_ERR("[N]L2capNoBufForSend");
return -ENOMEM;
}
rc = os_mbuf_append(sdu_tx, buf->data, buf->len);
if (rc) {
LOG_ERR("[N]L2capAppendBufFail[%d]", rc);
os_mbuf_free_chain(sdu_tx);
return -EIO;
}
rc = ble_l2cap_send(ble_chan, sdu_tx);
if (rc && rc != BLE_HS_ESTALLED) {
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 {
/* NimBLE only consumes the mbuf on success; free it on any failure. */
LOG_ERR("[N]L2capSendInternalFail[%d]", rc);
os_mbuf_free_chain(sdu_tx);
}
return nimble_err_to_errno(rc);
}
/* Payload now in sdu_tx; release caller's buf else ot_chan_tx_pool
* (1 entry) leaks. */
net_buf_unref(buf);
if (rc == BLE_HS_ESTALLED) {
/* Out of credits: queued, reported by COC_TX_UNSTALLED. If the conn
* drops while stalled, NimBLE fires only COC_DISCONNECTED; the OTS
* disconnected callback cleans up tx state, so the missing sent is
* harmless. */
LOG_DBG("[N]L2capMoreCreditsForSend");
return 0;
}
/* Fully transmitted; NimBLE raises COC_TX_UNSTALLED only for a stalled SDU,
* so this is the sole completion signal. */
bt_le_l2cap_post_sent(conn_handle_h, psm_h);
return 0;
}
int bt_le_nimble_l2cap_init(void)
{
int rc;
memset(ots_chans, 0, sizeof(ots_chans));
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]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]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);
/* NimBLE has no delete-server API, so the registration from a previous cycle
* survives deinit. It still points at ots_l2cap_event_cb, so treat EALREADY
* as success - failing here would make any re-init impossible. */
if (rc == BLE_HS_EALREADY) {
LOG_WRN("[N]L2capL2capSrvExist");
} else if (rc) {
LOG_ERR("[N]L2capCreateL2capSrvFail[%d]", rc);
return rc;
}
#endif /* CONFIG_BT_OTS */
return 0;
}
void bt_le_nimble_l2cap_deinit(void)
{
LOG_DBG("[N]L2capDeinit");
/* Nothing to release: ots_mem is static BSS and os_mempool_init re-registers
* an existing pool by name, so re-init needs no teardown here. The CoC server
* cannot be dropped either - NimBLE exposes no delete-server API - which is
* why init tolerates BLE_HS_EALREADY. */
memset(ots_chans, 0, sizeof(ots_chans));
}
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -310,6 +311,16 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
struct bt_gatt_ots_object *new_obj;
struct bt_ots_obj_created_desc created_desc;
/* Directory listing Object Type is 16- or 128-bit only (same as OACP Create). */
switch (param->type.uuid.type) {
case BT_UUID_TYPE_16:
case BT_UUID_TYPE_128:
break;
default:
LOG_ERR("OtsObjAddInvType[%u]", param->type.uuid.type);
return -EINVAL;
}
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
!bt_ots_dir_list_is_idle(ots->dir_list)) {
LOG_DBG("OtsDirListBusy");
@@ -330,6 +341,9 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
if (err) {
(void)bt_gatt_ots_obj_manager_obj_delete(new_obj);
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
}
return err;
}
@@ -359,11 +373,19 @@ int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
LOG_ERR("OtsObjCreatedCbNotSet");
(void)bt_gatt_ots_obj_manager_obj_delete(new_obj);
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
}
return -EINVAL;
}
new_obj->metadata.type = param->type;
new_obj->metadata.name = created_desc.name;
/* Own the name: a client name write copies into metadata.name, and the
* application's buffer may be read-only or shorter than the max name. */
strncpy(new_obj->metadata.name_c, created_desc.name, CONFIG_BT_OTS_OBJ_MAX_NAME_LEN);
new_obj->metadata.name_c[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN] = '\0';
new_obj->metadata.name = new_obj->metadata.name_c;
new_obj->metadata.size = created_desc.size;
new_obj->metadata.props = created_desc.props;
@@ -448,6 +470,11 @@ int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
return err;
}
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
/* Object removed from the list: drop stale anchor and refresh size. */
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
}
if (ots->cur_obj == obj) {
ots->cur_obj = NULL;
}
@@ -464,18 +491,48 @@ void *bt_ots_svc_decl_get(struct bt_ots *ots)
static void oacp_indicate_work_handler(struct k_work *work)
{
struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, struct bt_gatt_ots_indicate, work);
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct bt_gatt_ots_indicate *ind =
CONTAINER_OF(dwork, struct bt_gatt_ots_indicate, work);
struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, oacp_ind);
int err;
bt_gatt_indicate(NULL, &ots->oacp_ind.params);
if (!ind->conn) {
LOG_WRN("OtsOacpIndNoConn");
ots->oacp_ind.ind_in_flight = false;
return;
}
LOG_INF("OtsOacpInd[%04x]", ind->conn->handle);
err = bt_gatt_indicate(ind->conn, &ots->oacp_ind.params);
if (err) {
LOG_ERR("OtsOacpIndFail[%04x][%d]", ind->conn->handle, err);
ots->oacp_ind.ind_in_flight = false;
}
}
static void olcp_indicate_work_handler(struct k_work *work)
{
struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, struct bt_gatt_ots_indicate, work);
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct bt_gatt_ots_indicate *ind =
CONTAINER_OF(dwork, struct bt_gatt_ots_indicate, work);
struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, olcp_ind);
int err;
bt_gatt_indicate(NULL, &ots->olcp_ind.params);
if (!ind->conn) {
LOG_WRN("OtsOlcpIndNoConn");
ots->olcp_ind.ind_in_flight = false;
return;
}
LOG_INF("OtsOlcpInd[%04x]", ind->conn->handle);
err = bt_gatt_indicate(ind->conn, &ots->olcp_ind.params);
if (err) {
LOG_ERR("OtsOlcpIndFail[%04x][%d]", ind->conn->handle, err);
ots->olcp_ind.ind_in_flight = false;
}
}
int bt_ots_init(struct bt_ots *ots,
@@ -531,7 +588,11 @@ int bt_ots_init(struct bt_ots *ots,
err = bt_gatt_service_register(ots->service);
if (err) {
bt_gatt_ots_l2cap_unregister(&ots->l2cap);
int unreg_err = bt_gatt_ots_l2cap_unregister(&ots->l2cap);
if (unreg_err) {
LOG_ERR("OtsL2capUnregFail[%d]", unreg_err);
}
return err;
}
@@ -540,8 +601,8 @@ int bt_ots_init(struct bt_ots *ots,
bt_ots_dir_list_init(&ots->dir_list, ots->obj_manager);
}
k_work_init(&ots->oacp_ind.work, oacp_indicate_work_handler);
k_work_init(&ots->olcp_ind.work, olcp_indicate_work_handler);
k_work_init_delayable(&ots->oacp_ind.work, oacp_indicate_work_handler);
k_work_init_delayable(&ots->olcp_ind.work, olcp_indicate_work_handler);
LOG_DBG("OtsInit");
@@ -616,6 +677,7 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co
char id_str[BT_OTS_OBJ_ID_STR_LEN];
struct bt_gatt_ots_object *obj;
struct bt_gatt_ots_object *next_obj;
bool deleted = false;
int err;
err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &next_obj);
@@ -638,9 +700,34 @@ static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *co
if (bt_gatt_ots_obj_manager_obj_delete(obj)) {
LOG_ERR("OtsObjMgrDelFail[%s]",
id_str);
} else {
deleted = true;
}
}
}
/* Refresh once after the loop: while empty-name objects are being deleted
* the manager still holds not-yet-removed ones, and dir_list_update_size
* would assert (name_len > 0) on them.
*/
if (deleted && IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list) {
bt_ots_dir_list_content_changed(ots->dir_list, ots->obj_manager);
}
}
static void ots_ind_on_disconnect(struct bt_gatt_ots_indicate *ind, struct bt_conn *conn)
{
if (ind->conn != conn) {
LOG_INF("OtsIndSkipDisc[%04x][%04x]",
ind->conn ? ind->conn->handle : 0xFFFF, conn->handle);
return;
}
LOG_INF("OtsIndClrOnDisc[%04x]", conn->handle);
(void)k_work_cancel_delayable(&ind->work);
ind->ind_in_flight = false;
ind->conn = NULL;
}
static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason)
@@ -654,6 +741,9 @@ static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason)
LOG_DBG("OtsInstDisconnect[%u]", index);
ots_ind_on_disconnect(&instance->oacp_ind, conn);
ots_ind_on_disconnect(&instance->olcp_ind, conn);
if (instance->cur_obj != NULL) {
__ASSERT(instance->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE,
"The current object is expected to be in idle state as part "
@@ -678,7 +768,7 @@ struct bt_ots *bt_ots_free_instance_get(void)
return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++];
}
static int bt_gatt_ots_instances_prepare(void)
int bt_gatt_ots_instances_prepare(void)
{
uint32_t index;
struct bt_ots *instance;
@@ -709,10 +799,12 @@ static int bt_gatt_ots_instances_prepare(void)
return 0;
}
SYS_INIT(bt_gatt_ots_instances_prepare, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
int bt_gatt_ots_conn_cb_register(void)
{
return bt_conn_cb_register_safe((void *)&bt_conn_cb_conn_callbacks);
}
void bt_gatt_ots_conn_cb_unregister(void)
{
(void)bt_conn_cb_unregister_safe((void *)&bt_conn_cb_conn_callbacks);
}
@@ -2,6 +2,7 @@
* @brief Bluetooth Object Transfer Client
*
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -131,6 +132,7 @@ static void read_next_metadata(struct bt_conn *conn,
static int read_attr(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst,
uint16_t handle, bt_gatt_read_func_t cb);
static void oacp_clr_cur_inst(struct bt_otc_internal_instance_t *inst);
/* L2CAP callbacks */
static void tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
@@ -177,6 +179,7 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
}
const uint32_t offset = cur_inst->rcvd_size;
uint32_t len = buf->len;
bool is_complete = false;
const struct bt_ots_obj_metadata *cur_object =
&cur_inst->otc_inst->cur_object;
@@ -184,19 +187,24 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
LOG_DBG("OtsCliL2capRecv[%u][%u]", buf->len, offset);
cur_inst->rcvd_size += buf->len;
if (cur_inst->rcvd_size >= cur_object->size.cur) {
is_complete = true;
if (offset >= cur_object->size.cur) {
LOG_WRN("OtsCliRecvPastEnd[%u][%u]", offset, cur_object->size.cur);
(void)bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
cur_inst = NULL;
return -EMSGSIZE;
}
if (cur_inst->rcvd_size > cur_object->size.cur) {
LOG_WRN("OtsCliRecvExceedMax[%u][%u]", cur_inst->rcvd_size, cur_object->size.cur);
if (offset + len > cur_object->size.cur) {
LOG_WRN("OtsCliRecvExceedMax[%u][%u]", offset + len, cur_object->size.cur);
len = cur_object->size.cur - offset;
}
cur_inst->rcvd_size = offset + len;
is_complete = (cur_inst->rcvd_size >= cur_object->size.cur);
if (cur_inst->otc_inst->cb != NULL && cur_inst->otc_inst->cb->obj_data_read != NULL) {
cb_ret = cur_inst->otc_inst->cb->obj_data_read(cur_inst->otc_inst, conn, offset,
buf->len, buf->data,
len, buf->data,
is_complete);
} else {
LOG_ERR("OtsCliObjDataRdCbNull");
@@ -234,10 +242,15 @@ static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
static void chan_closed(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn)
{
struct bt_otc_internal_instance_t *inst =
CONTAINER_OF(l2cap_ctx, struct bt_otc_internal_instance_t, l2cap_ctx);
ARG_UNUSED(conn);
LOG_DBG("OtsCliL2capClosed");
if (cur_inst) {
cur_inst = NULL;
}
/* Only release if this channel belonged to the active transfer. */
oacp_clr_cur_inst(inst);
}
/* End L2CAP callbacks */
@@ -318,7 +331,9 @@ static void olcp_ind_handler(struct bt_conn *conn,
enum bt_gatt_ots_olcp_proc_type op_code;
struct net_buf_simple net_buf;
if (length < sizeof(op_code)) {
/* Op Code is 1 byte on the wire; sizeof(enum) is 4 here and would reject
* every valid 3-byte response. */
if (length < sizeof(uint8_t)) {
LOG_WRN("OtsCliInvIndLen[%u]", length);
return;
}
@@ -387,18 +402,29 @@ static void olcp_ind_handler(struct bt_conn *conn,
}
}
static void oacp_clr_cur_inst(struct bt_otc_internal_instance_t *inst)
{
if (cur_inst == inst) {
cur_inst = NULL;
}
}
static void oacp_ind_handler(struct bt_conn *conn,
struct bt_ots_client *otc_inst,
struct bt_otc_internal_instance_t *inst,
const void *data, uint16_t length)
{
struct bt_ots_client *otc_inst = inst->otc_inst;
enum bt_gatt_ots_oacp_proc_type op_code;
enum bt_gatt_ots_oacp_proc_type req_opcode;
enum bt_gatt_ots_oacp_res_code result_code;
uint32_t checksum;
struct net_buf_simple net_buf;
if (length < sizeof(op_code)) {
/* Op Code is 1 byte on the wire; sizeof(enum) is 4 here and would reject
* every valid 3-byte response. */
if (length < sizeof(uint8_t)) {
LOG_WRN("OtsCliInvIndLen[%u]", length);
oacp_clr_cur_inst(inst);
return;
}
@@ -409,11 +435,12 @@ static void oacp_ind_handler(struct bt_conn *conn,
LOG_DBG("OtsCliOacpInd");
if (op_code == BT_GATT_OTS_OACP_PROC_RESP) {
if (net_buf.len >= (sizeof(req_opcode) + sizeof(result_code))) {
if (net_buf.len >= (sizeof(uint8_t) + sizeof(uint8_t))) {
req_opcode = net_buf_simple_pull_u8(&net_buf);
result_code = net_buf_simple_pull_u8(&net_buf);
} else {
LOG_WRN("OtsCliInvIndDataLen[%u]", net_buf.len);
oacp_clr_cur_inst(inst);
return;
}
@@ -427,13 +454,22 @@ static void oacp_ind_handler(struct bt_conn *conn,
}
} else {
LOG_WRN("OtsCliInvChecksumLen[%u]", net_buf.len);
return;
}
/* Checksum never uses L2CAP; always release cur_inst. */
oacp_clr_cur_inst(inst);
} else if ((req_opcode == BT_GATT_OTS_OACP_PROC_READ ||
req_opcode == BT_GATT_OTS_OACP_PROC_WRITE) &&
result_code != BT_GATT_OTS_OACP_RES_SUCCESS) {
/* Read/Write SUCCESS still waits on L2CAP (rx/tx_done/closed). */
LOG_DBG("OtsCliOacpFailClr[%02x][%02x]", req_opcode, result_code);
oacp_clr_cur_inst(inst);
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
}
print_oacp_response(req_opcode, result_code);
} else {
LOG_WRN("OtsCliInvIndOpcode[%u]", op_code);
oacp_clr_cur_inst(inst);
}
}
@@ -465,7 +501,7 @@ uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn,
if (handle == inst->otc_inst->olcp_handle) {
olcp_ind_handler(conn, inst->otc_inst, data, length);
} else if (handle == inst->otc_inst->oacp_handle) {
oacp_ind_handler(conn, inst->otc_inst, data, length);
oacp_ind_handler(conn, inst, data, length);
}
}
return BT_GATT_ITER_CONTINUE;
@@ -511,8 +547,50 @@ static uint8_t read_feature_cb(struct bt_conn *conn, uint8_t err,
return BT_GATT_ITER_STOP;
}
/* Disconnect fires no subscription callback, so the OLCP/OACP indication that
* releases busy never arrives — an in-flight procedure would strand -EBUSY. */
static void ots_client_conn_disconnected(struct bt_conn *conn, uint8_t reason)
{
ARG_UNUSED(conn);
ARG_UNUSED(reason);
for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
struct bt_otc_internal_instance_t *inst = &otc_insts[i];
if (inst->otc_inst == NULL || !inst->busy) {
continue;
}
LOG_WRN("OtsCliDisconnBusy[%d]", i);
inst->busy = false;
oacp_clr_cur_inst(inst);
}
}
BT_CONN_CB_DEFINE(client_conn_callbacks) = {
.disconnected = ots_client_conn_disconnected,
};
int bt_gatt_ots_client_conn_cb_register(void)
{
return bt_conn_cb_register_safe((void *)&bt_conn_cb_client_conn_callbacks);
}
void bt_gatt_ots_client_conn_cb_unregister(void)
{
(void)bt_conn_cb_unregister_safe((void *)&bt_conn_cb_client_conn_callbacks);
}
int bt_ots_client_register(struct bt_ots_client *otc_inst)
{
/* A NULL inst would leave the slot marked free after registering its
* L2CAP node, so the next call re-appends the same node and self-links it. */
if (otc_inst == NULL) {
LOG_ERR("OtsCliInvInst");
return -EINVAL;
}
for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
int err;
@@ -534,18 +612,32 @@ int bt_ots_client_register(struct bt_ots_client *otc_inst)
return -ENOMEM;
}
__attribute__((unused))
int bt_ots_client_unregister(uint8_t index)
{
if (index < ARRAY_SIZE(otc_insts)) {
bt_gatt_ots_l2cap_unregister(&otc_insts[index].l2cap_ctx);
memset(&otc_insts[index], 0, sizeof(otc_insts[index]));
} else {
int err;
if (index >= ARRAY_SIZE(otc_insts)) {
return -EINVAL;
}
err = bt_gatt_ots_l2cap_unregister(&otc_insts[index].l2cap_ctx);
if (err) {
LOG_WRN("OtsCliL2capUnregFail[%d]", err);
return err;
}
if (cur_inst == &otc_insts[index]) {
LOG_DBG("OtsCliUnregClrCurInst[%u]", index);
cur_inst = NULL;
}
memset(&otc_insts[index], 0, sizeof(otc_insts[index]));
return 0;
}
__attribute__((unused))
int bt_ots_client_read_feature(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
@@ -602,7 +694,15 @@ static void write_olcp_cb(struct bt_conn *conn, uint8_t err,
return;
}
inst->busy = false;
/* Keep busy until OLCP indication; only release on write failure.
* Notify the app — without an indication, obj_selected would never fire.
*/
if (err) {
LOG_WRN("OtsCliOlcpWrFail[%02x]", err);
inst->busy = false;
on_object_selected(conn, BT_GATT_OTS_OLCP_RES_OPERATION_FAILED,
inst->otc_inst);
}
}
static int write_olcp(struct bt_otc_internal_instance_t *inst,
@@ -633,6 +733,7 @@ static int write_olcp(struct bt_otc_internal_instance_t *inst,
return err;
}
__attribute__((unused))
int bt_ots_client_select_id(struct bt_ots_client *otc_inst,
struct bt_conn *conn,
uint64_t obj_id)
@@ -679,6 +780,7 @@ int bt_ots_client_select_id(struct bt_ots_client *otc_inst,
return -EOPNOTSUPP;
}
__attribute__((unused))
int bt_ots_client_select_first(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
@@ -713,6 +815,7 @@ int bt_ots_client_select_first(struct bt_ots_client *otc_inst,
return -EOPNOTSUPP;
}
__attribute__((unused))
int bt_ots_client_select_last(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
@@ -748,6 +851,7 @@ int bt_ots_client_select_last(struct bt_ots_client *otc_inst,
return -EOPNOTSUPP;
}
__attribute__((unused))
int bt_ots_client_select_next(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
@@ -782,6 +886,7 @@ int bt_ots_client_select_next(struct bt_ots_client *otc_inst,
return -EOPNOTSUPP;
}
__attribute__((unused))
int bt_ots_client_select_prev(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
@@ -1040,6 +1145,7 @@ static uint8_t read_obj_created_cb(struct bt_conn *conn, uint8_t err,
date_time_decode(
&net_buf,
&inst->otc_inst->cur_object.first_created);
BT_OTS_SET_METADATA_REQ_CREATED(inst->metadata_read);
} else {
LOG_WRN("OtsCliInvLen[%u][%u]", length, BT_OTS_DATE_TIME_FIELD_SIZE);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
@@ -1079,6 +1185,7 @@ static uint8_t read_obj_modified_cb(struct bt_conn *conn, uint8_t err,
if (length == BT_OTS_DATE_TIME_FIELD_SIZE) {
date_time_decode(&net_buf,
&inst->otc_inst->cur_object.modified);
BT_OTS_SET_METADATA_REQ_MODIFIED(inst->metadata_read);
} else {
LOG_WRN("OtsCliInvLen[%u][%u]", length, BT_OTS_DATE_TIME_FIELD_SIZE);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
@@ -1102,8 +1209,9 @@ static int read_attr(struct bt_conn *conn,
uint16_t handle, bt_gatt_read_func_t cb)
{
if (!handle) {
/* Characteristic not discovered — skip without poisoning metadata_err. */
LOG_DBG("OtsCliHdlNotSet");
return -EINVAL;
return -ENOENT;
} else if (cb == NULL) {
LOG_ERR("OtsCliCbNull");
return -EINVAL;
@@ -1138,22 +1246,24 @@ static uint8_t read_obj_properties_cb(struct bt_conn *conn, uint8_t err,
if (err) {
LOG_WRN("OtsCliMetaRdErr[%02x]", err);
} else if (data && length == OTS_PROPERTIES_LEN) {
struct bt_ots_obj_metadata *cur_object =
&inst->otc_inst->cur_object;
} else if (data) {
if (length != OTS_PROPERTIES_LEN) {
LOG_WRN("OtsCliInvLen[%u][%u]", length, OTS_PROPERTIES_LEN);
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
} else {
struct bt_ots_obj_metadata *cur_object =
&inst->otc_inst->cur_object;
cur_object->props = net_buf_simple_pull_le32(&net_buf);
cur_object->props = net_buf_simple_pull_le32(&net_buf);
LOG_INF("OtsCliObjPropsRaw[%x]", cur_object->props);
LOG_INF("OtsCliObjPropsRaw[%x]", cur_object->props);
if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) {
LOG_WRN("OtsCliObjRdNotSupp");
if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) {
LOG_WRN("OtsCliObjRdNotSupp");
}
BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read);
}
BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read);
} else {
LOG_WRN("OtsCliInvLen[%u][%u]", length, OTS_PROPERTIES_LEN);
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
if (cb_err) {
@@ -1181,7 +1291,14 @@ static void write_oacp_cp_cb(struct bt_conn *conn, uint8_t err,
return;
}
inst->busy = false;
/* Keep busy until OACP indication; only release on write failure. */
if (err) {
LOG_WRN("OtsCliOacpWrFail[%02x]", err);
inst->busy = false;
oacp_clr_cur_inst(inst);
/* oacp_read may already have connected L2CAP before the GATT write. */
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
}
}
static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
@@ -1201,6 +1318,7 @@ static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
LOG_WRN("OtsCliOacpWrReqFail[%02x]", err);
inst->busy = false;
cur_inst = NULL;
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
return;
}
@@ -1210,6 +1328,7 @@ static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err,
if (err) {
LOG_WRN("OtsCliL2capSendErr[%d]", err);
cur_inst = NULL;
(void)bt_gatt_ots_l2cap_disconnect(&inst->l2cap_ctx);
}
inst->busy = false;
@@ -1236,6 +1355,7 @@ static int oacp_read(struct bt_conn *conn,
* transfer?
*/
l2cap = &inst->l2cap_ctx;
err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
if (err) {
LOG_WRN("OtsCliL2capConnectFail[%d]", err);
@@ -1269,6 +1389,8 @@ static int oacp_read(struct bt_conn *conn,
if (!err) {
inst->busy = true;
cur_inst = inst;
} else {
(void)bt_gatt_ots_l2cap_disconnect(l2cap);
}
inst->rcvd_size = 0;
@@ -1292,6 +1414,7 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i
return -EBUSY;
}
l2cap = &inst->l2cap_ctx;
err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
if (err) {
LOG_WRN("OtsCliL2capConnectFail[%d]", err);
@@ -1328,6 +1451,8 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i
if (!err) {
inst->busy = true;
cur_inst = inst;
} else {
(void)bt_gatt_ots_l2cap_disconnect(l2cap);
}
inst->rcvd_size = 0;
@@ -1405,6 +1530,7 @@ int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst,
return oacp_read(conn, inst);
}
__attribute__((unused))
int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst,
struct bt_conn *conn, const void *buf, size_t len,
off_t offset, enum bt_ots_oacp_write_op_mode mode)
@@ -1472,6 +1598,7 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst,
return oacp_write(conn, inst, buf, (uint32_t)len, (uint32_t)offset, mode);
}
__attribute__((unused))
int bt_ots_client_get_object_checksum(struct bt_ots_client *otc_inst, struct bt_conn *conn,
off_t offset, size_t len)
{
@@ -1569,6 +1696,10 @@ static void read_next_metadata(struct bt_conn *conn,
if (err) {
LOG_INF("OtsCliMetaRdFailTryNext[%d]", err);
/* -ENOENT: handle not discovered, skip. Other errors: keep first. */
if (err != -ENOENT && !inst->metadata_err) {
inst->metadata_err = err;
}
read_next_metadata(conn, inst);
}
}
@@ -1756,6 +1887,7 @@ static int decode_record(struct net_buf_simple *buf,
return rec->len;
}
__attribute__((unused))
int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length,
bt_ots_client_dirlisting_cb cb)
{
@@ -1772,6 +1904,9 @@ int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length,
while (net_buf.len) {
int ret;
/* Optional fields written only when flagged; clear between records. */
memset(&record, 0, sizeof(record));
count++;
if (net_buf.len < sizeof(uint16_t)) {
@@ -4,6 +4,7 @@
* For use with the Object Transfer Service Client (OTC)
*
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2021 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -263,6 +264,12 @@ static void dir_list_update_size(struct bt_ots_dir_list *dir_list, void *obj_man
dir_list->dir_list_obj->metadata.size.cur = len;
}
void bt_ots_dir_list_content_changed(struct bt_ots_dir_list *dir_list, void *obj_manager)
{
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
dir_list_update_size(dir_list, obj_manager);
}
void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager,
struct bt_gatt_ots_object *cur_obj)
{
@@ -273,8 +280,7 @@ void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manage
return;
}
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
dir_list_update_size(dir_list, obj_manager);
bt_ots_dir_list_content_changed(dir_list, obj_manager);
}
void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager)
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2021 - 2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -158,6 +159,7 @@ enum {
void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager,
struct bt_gatt_ots_object *cur_obj);
void bt_ots_dir_list_content_changed(struct bt_ots_dir_list *dir_list, void *obj_manager);
void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager);
ssize_t bt_ots_dir_list_content_get(struct bt_ots_dir_list *dir_list, void *obj_manager,
void **data, size_t len, off_t offset);
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,6 +13,7 @@ extern "C" {
#endif
#include <zephyr/types.h>
#include <../host/conn_internal.h>
#include "ots_l2cap_internal.h"
#include "ots_oacp_internal.h"
#include "ots_olcp_internal.h"
@@ -122,7 +124,11 @@ struct bt_gatt_ots_indicate {
struct bt_gatt_attr attr;
struct bt_gatt_ccc_managed_user_data ccc;
bool is_enabled;
struct k_work work;
/* True from schedule until indication confirm (or indicate submit fail). */
bool ind_in_flight;
/* Peer that wrote the control point; indication must target this conn only. */
struct bt_conn *conn;
struct k_work_delayable work;
uint8_t res[OACP_OLCP_RES_MAX_SIZE];
};
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -46,8 +47,9 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
len = MIN(l2cap_ctx->ot_chan.tx.mtu, CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU);
len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent);
/* Prepare buffer for sending. */
buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER);
/* Single-buffer pool, and iso_task is both the only allocator and the only
* path that releases it, so waiting here would self-deadlock. */
buf = net_buf_alloc(&ot_chan_tx_pool, K_NO_WAIT);
if (buf == NULL) {
LOG_ERR("OtsL2capTxBufAllocFail");
return -ENOMEM;
@@ -77,7 +79,7 @@ static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
{
LOG_DBG("OtsL2capAllocBuf");
return net_buf_alloc(&ot_chan_rx_pool, K_FOREVER);
return net_buf_alloc(&ot_chan_rx_pool, K_NO_WAIT);
}
#endif
@@ -93,12 +95,11 @@ static void l2cap_sent(struct bt_l2cap_chan *chan)
/* Ongoing TX - sending next chunk. */
if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) {
if (ots_l2cap_send(l2cap_ctx)) {
/* Send failed - clean up TX state to unblock channel. */
/* Do not call tx_done: it means success to upper layers
* (oacp_read would skip unread bytes; write_obj_tx_done
* would report a full write). Abort via disconnect → closed. */
LOG_WRN("OtsL2capTxAbort");
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
if (l2cap_ctx->tx_done) {
l2cap_ctx->tx_done(l2cap_ctx, chan->conn);
}
(void)bt_l2cap_chan_disconnect(chan);
}
return;
@@ -155,6 +156,12 @@ static void l2cap_disconnected(struct bt_l2cap_chan *chan)
if (l2cap_ctx->closed) {
l2cap_ctx->closed(l2cap_ctx, chan->conn);
}
/* Contexts are reused via find_free_l2cap_ctx; drop procedure hooks
* so a later accept/connect cannot invoke stale callbacks. */
l2cap_ctx->closed = NULL;
l2cap_ctx->rx_done = NULL;
l2cap_ctx->tx_done = NULL;
}
static const struct bt_l2cap_chan_ops l2cap_ops = {
@@ -216,7 +223,7 @@ static struct bt_l2cap_server l2cap_server = {
.accept = l2cap_accept,
};
static int bt_gatt_ots_l2cap_init(void)
int bt_gatt_ots_l2cap_init(void)
{
int err;
@@ -268,6 +275,10 @@ int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
if (sys_slist_find(&channels, &l2cap_ctx->node, NULL)) {
return -EALREADY;
}
sys_slist_append(&channels, &l2cap_ctx->node);
return 0;
@@ -275,6 +286,11 @@ int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx)
int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
if (l2cap_ctx->ot_chan.chan.conn) {
LOG_WRN("OtsL2capUnregBusy");
return -EBUSY;
}
sys_slist_find_and_remove(&channels, &l2cap_ctx->node);
return 0;
@@ -297,11 +313,21 @@ int bt_gatt_ots_l2cap_connect(struct bt_conn *conn,
return -EINVAL;
}
/* Callers owning a context pass it in, so the context they later use in
* their callbacks is the one actually connected. The global free-list
* lookup would otherwise hand out another instance's (or the server's)
* context whenever more than one is registered. */
ctx = *l2cap_ctx;
*l2cap_ctx = NULL;
ctx = find_free_l2cap_ctx();
if (!ctx) {
return -ENOMEM;
ctx = find_free_l2cap_ctx();
if (!ctx) {
return -ENOMEM;
}
} else if (ctx->ot_chan.chan.conn) {
LOG_WRN("OtsL2capCtxAlreadyConnected");
return -EBUSY;
}
l2cap_chan_init(&ctx->ot_chan);
@@ -323,6 +349,3 @@ int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx)
{
return bt_l2cap_chan_disconnect(&l2cap_ctx->ot_chan.chan);
}
SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION,
CONFIG_APPLICATION_INIT_PRIORITY);
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -49,11 +50,12 @@ int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx);
/** @brief Connect OTS L2CAP channel
*
* This function is for the OTS client to make an L2CAP connection to
* the OTS server. One of the available registered L2CAP contexts
* will be used for the connection.
* the OTS server.
*
* @param[in] conn Connection pointer
* @param[out] l2cap_ctx The context that was connected
* @param[in] conn Connection pointer
* @param[in,out] l2cap_ctx On entry, the caller's context to connect, or NULL
* to pick any free registered one. On success, set
* to the connected context; NULL on failure.
*
* @return 0 in case of success or negative value in case of error
*/
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -37,13 +38,16 @@ static void oacp_l2cap_closed(struct bt_gatt_ots_l2cap *l2cap_ctx,
ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap);
/* Always drop procedure sinks — cur_obj may already be cleared (e.g.
* deleted while idle after a failed send that left callbacks set). */
l2cap_ctx->rx_done = NULL;
l2cap_ctx->tx_done = NULL;
if (!ots->cur_obj) {
return;
}
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
l2cap_ctx->rx_done = NULL;
l2cap_ctx->tx_done = NULL;
}
#if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT)
@@ -68,6 +72,12 @@ static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate(
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
}
if (ots->cur_obj &&
ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
LOG_DBG("OtsOacpCreateObjLocked");
return BT_GATT_OTS_OACP_RES_OBJ_LOCKED;
}
err = bt_ots_obj_add_internal(ots, conn, &param, &obj);
if (err) {
goto exit;
@@ -216,6 +226,11 @@ static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
LOG_DBG("OtsOacpValRd[%08x][%08x]", params->offset, params->len);
if (!BT_OTS_OACP_GET_FEAT_READ(ots->features.oacp)) {
LOG_DBG("OtsOacpRdNotSupp");
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
}
if (!ots->cur_obj) {
return BT_GATT_OTS_OACP_RES_INV_OBJ;
}
@@ -297,8 +312,9 @@ static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate(
return BT_GATT_OTS_OACP_RES_INV_PARAM;
}
/* append is not supported */
if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.cur) {
/* Growing the object is allowed (see the size.cur update in
* oacp_write_proc_cb), but never past what the application allocated. */
if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.alloc) {
return BT_GATT_OTS_OACP_RES_INV_PARAM;
}
@@ -511,11 +527,31 @@ static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
return;
}
/* Early EOF while bytes remain: do not L2CAP-send len 0 (tx_done would
* re-enter with sent_len unchanged → busy loop).
*/
if (len == 0) {
LOG_WRN("OtsOacpRdEofEarly[%u][%u]", read_op->sent_len,
read_op->oacp_params.len);
bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
return;
}
ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0, offset);
return;
}
ots->l2cap.tx_done = oacp_read_proc_cb;
ots->l2cap.closed = oacp_l2cap_closed;
err = bt_gatt_ots_l2cap_send(&ots->l2cap, obj_chunk, len);
if (err) {
LOG_WRN("OtsOacpL2capErr[%d]", err);
bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
} else {
read_op->sent_len += len;
@@ -557,9 +593,17 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
return -ENODEV;
}
/* Reading state.write_op while another procedure owns the union would
* confuse recv_len with the read op's fields and underflow len below. */
if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_WRITE_OP_STATE) {
LOG_ERR("OtsOacpWrInvState[%d]", ots->cur_obj->state.type);
return -EINVAL;
}
if (!ots->cb->obj_write) {
LOG_ERR("OtsOacpWrNoCb");
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
l2cap_ctx->rx_done = NULL;
return -ENODEV;
}
@@ -608,6 +652,13 @@ static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
ots->cur_obj->metadata.size.cur = offset + len;
}
/* Back to idle means this write is over (completed or failed above). The
* CoC stays open, so drop the sink or a later procedure's inbound data
* would still land here. */
if (ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE) {
l2cap_ctx->rx_done = NULL;
}
return rc;
}
#endif
@@ -620,11 +671,28 @@ static void oacp_ind_cb(struct bt_conn *conn,
LOG_DBG("OtsOacpRecvIndAck[%04x]", err);
ots->oacp_ind.ind_in_flight = false;
ots->oacp_ind.conn = NULL;
if (!ots->cur_obj) {
LOG_DBG("OtsOacpNoObjForAck");
return;
}
if (err) {
/* Client did not ACK the OACP response — do not start L2CAP I/O.
* Leave READ/WRITE would permanently OBJ_LOCKED. */
LOG_WRN("OtsOacpIndFail[%02x][%d]", err, ots->cur_obj->state.type);
if (ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_READ_OP_STATE ||
ots->cur_obj->state.type == BT_GATT_OTS_OBJECT_WRITE_OP_STATE) {
ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE;
ots->l2cap.rx_done = NULL;
ots->l2cap.tx_done = NULL;
(void)bt_gatt_ots_l2cap_disconnect(&ots->l2cap);
}
return;
}
switch (ots->cur_obj->state.type) {
case BT_GATT_OTS_OBJECT_READ_OP_STATE:
oacp_read_proc_execute(ots, conn);
@@ -641,7 +709,8 @@ static void oacp_ind_cb(struct bt_conn *conn,
}
}
static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
static void oacp_ind_send(struct bt_conn *conn,
const struct bt_gatt_attr *oacp_attr,
struct bt_gatt_ots_oacp_proc oacp_proc,
enum bt_gatt_ots_oacp_res_code oacp_status,
struct net_buf_simple *resp_param)
@@ -649,6 +718,7 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
struct bt_ots *ots = (struct bt_ots *) oacp_attr->user_data;
uint8_t *oacp_res = ots->oacp_ind.res;
uint16_t oacp_res_len = 0;
int err;
/* Encode OACP Response */
oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP;
@@ -669,9 +739,16 @@ static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr,
ots->oacp_ind.params.data = oacp_res;
ots->oacp_ind.params.len = oacp_res_len;
LOG_DBG("OtsOacpSendInd");
LOG_DBG("OtsOacpSendInd[%u]", conn->handle);
k_work_submit(&ots->oacp_ind.work);
ots->oacp_ind.conn = conn;
ots->oacp_ind.ind_in_flight = true;
err = k_work_schedule(&ots->oacp_ind.work, K_NO_WAIT_ASYNC);
if (err < 0) {
LOG_ERR("OtsOacpSchIndFail[%u][%d]", conn->handle, err);
ots->oacp_ind.ind_in_flight = false;
ots->oacp_ind.conn = NULL;
}
}
ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
@@ -697,7 +774,8 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (k_work_is_pending(&ots->oacp_ind.work)) {
if (ots->oacp_ind.ind_in_flight ||
k_work_is_pending(&ots->oacp_ind.work.work)) {
LOG_WRN("OtsOacpWrBeforeIndSent");
return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS);
}
@@ -725,7 +803,7 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
oacp_ind_send(attr, oacp_proc, oacp_status, &resp_param);
oacp_ind_send(conn, attr, oacp_proc, oacp_status, &resp_param);
return len;
}
@@ -741,5 +819,10 @@ void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr,
oacp_ind->is_enabled = false;
if (value == BT_GATT_CCC_INDICATE) {
oacp_ind->is_enabled = true;
} else {
LOG_DBG("OtsOacpIndClrOnCcc");
(void)k_work_cancel_delayable(&oacp_ind->work);
oacp_ind->ind_in_flight = false;
oacp_ind->conn = NULL;
}
}
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -185,7 +186,7 @@ int bt_gatt_ots_obj_manager_obj_add(
if (!cur_obj->is_allocated) {
cur_obj->is_allocated = true;
/* TODO: do we need to reset cur_obj->val to 0 here? */
(void)memset(&cur_obj->val, 0, sizeof(cur_obj->val));
cur_obj->val.id = obj_index_to_id(i);
sys_dlist_append(&obj_manager->list, &cur_obj->dnode);
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -200,16 +201,23 @@ static void olcp_ind_cb(struct bt_conn *conn,
struct bt_gatt_indicate_params *params,
uint8_t err)
{
struct bt_ots *ots = (struct bt_ots *) params->attr->user_data;
LOG_DBG("OtsOlcpRecvIndAck[%04x]", err);
ots->olcp_ind.ind_in_flight = false;
ots->olcp_ind.conn = NULL;
}
static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr,
static void olcp_ind_send(struct bt_conn *conn,
const struct bt_gatt_attr *olcp_attr,
enum bt_gatt_ots_olcp_proc_type req_op_code,
enum bt_gatt_ots_olcp_res_code olcp_status)
{
struct bt_ots *ots = (struct bt_ots *) olcp_attr->user_data;
uint8_t *olcp_res = ots->olcp_ind.res;
uint16_t olcp_res_len = 0;
int err;
/* Encode OLCP Response */
olcp_res[olcp_res_len++] = BT_GATT_OTS_OLCP_PROC_RESP;
@@ -219,7 +227,7 @@ static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr,
/* Prepare indication parameters */
memset(&ots->olcp_ind.params, 0, sizeof(ots->olcp_ind.params));
memcpy(&ots->olcp_ind.attr, olcp_attr, sizeof(ots->olcp_ind.attr));
ots->olcp_ind.params.attr = olcp_attr;
ots->olcp_ind.params.attr = &ots->olcp_ind.attr;
ots->olcp_ind.params.func = olcp_ind_cb;
ots->olcp_ind.params.data = olcp_res;
ots->olcp_ind.params.len = olcp_res_len;
@@ -227,9 +235,16 @@ static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr,
ots->olcp_ind.params.chan_opt = BT_ATT_CHAN_OPT_NONE;
#endif /* CONFIG_BT_EATT */
LOG_DBG("OtsOlcpSendInd");
LOG_DBG("OtsOlcpSendInd[%u]", conn->handle);
k_work_submit(&ots->olcp_ind.work);
ots->olcp_ind.conn = conn;
ots->olcp_ind.ind_in_flight = true;
err = k_work_schedule(&ots->olcp_ind.work, K_NO_WAIT_ASYNC);
if (err < 0) {
LOG_ERR("OtsOlcpSchIndFail[%u][%d]", conn->handle, err);
ots->olcp_ind.ind_in_flight = false;
ots->olcp_ind.conn = NULL;
}
}
ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
@@ -255,11 +270,18 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (k_work_is_pending(&ots->olcp_ind.work)) {
if (ots->olcp_ind.ind_in_flight ||
k_work_is_pending(&ots->olcp_ind.work.work)) {
LOG_WRN("OtsOlcpWrBeforeIndSent");
return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS);
}
if (ots->cur_obj &&
ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
LOG_WRN("OtsOlcpWrObjBusy[%d]", ots->cur_obj->state.type);
return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS);
}
old_obj = ots->cur_obj;
decode_status = olcp_command_decode(buf, len, &olcp_proc);
@@ -300,7 +322,7 @@ ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn,
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
olcp_ind_send(attr, olcp_proc.type, olcp_status);
olcp_ind_send(conn, attr, olcp_proc.type, olcp_status);
return len;
}
@@ -316,5 +338,10 @@ void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr,
olcp_ind->is_enabled = false;
if (value == BT_GATT_CCC_INDICATE) {
olcp_ind->is_enabled = true;
} else {
LOG_DBG("OtsOlcpIndClrOnCcc");
(void)k_work_cancel_delayable(&olcp_ind->work);
olcp_ind->ind_in_flight = false;
olcp_ind->conn = NULL;
}
}
@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Nordic Semiconductor ASA
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1505,6 +1505,7 @@ struct bt_bap_unicast_server_cb {
*
* @return 0 in case of success, negative error code otherwise.
*/
int bt_bap_unicast_server_register(const struct bt_bap_unicast_server_register_param *param);
int bt_bap_unicast_server_register_safe(const struct bt_bap_unicast_server_register_param *param);
/**
@@ -1521,6 +1522,7 @@ int bt_bap_unicast_server_register_safe(const struct bt_bap_unicast_server_regis
*
* @return 0 in case of success, negative error code otherwise.
*/
int bt_bap_unicast_server_unregister(void);
int bt_bap_unicast_server_unregister_safe(void);
/**
@@ -2695,6 +2697,7 @@ int bt_bap_broadcast_sink_delete_safe(struct bt_bap_broadcast_sink *sink);
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_bap_scan_delegator_register(struct bt_bap_scan_delegator_cb *cb);
int bt_bap_scan_delegator_register_safe(struct bt_bap_scan_delegator_cb *cb);
/**
@@ -2705,6 +2708,7 @@ int bt_bap_scan_delegator_register_safe(struct bt_bap_scan_delegator_cb *cb);
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_bap_scan_delegator_unregister(void);
int bt_bap_scan_delegator_unregister_safe(void);
/**
@@ -66,19 +66,19 @@ enum bt_gmap_ugg_feat {
/**
* @brief Support transmitting multiple LC3 codec frames per block in an SDU
*
* Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 0
* Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 0
*/
BT_GMAP_UGG_FEAT_MULTIPLEX = BIT(0),
/**
* @brief 96 kbps source support
*
* Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 0
* Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 0
*/
BT_GMAP_UGG_FEAT_96KBPS_SOURCE = BIT(1),
/**
* @brief Support for receiving at least two channels of audio, each in a separate CIS
*
* Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 1 and
* Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 1 and
* @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT} > 1
*/
BT_GMAP_UGG_FEAT_MULTISINK = BIT(2),
@@ -392,6 +392,7 @@ struct bt_has_preset_register_param {
*
* @return 0 if success, errno on failure.
*/
int bt_has_register(const struct bt_has_features_param *features);
int bt_has_register_safe(const struct bt_has_features_param *features);
/**
@@ -1601,6 +1601,7 @@ int bt_media_proxy_pl_register_safe(struct media_proxy_pl_calls *pl_calls);
*
* TODO: Move to player header file
*/
int bt_media_proxy_pl_init(void);
int bt_media_proxy_pl_init_safe(void);
/**
@@ -94,6 +94,7 @@ struct bt_micp_included {
*
* @return 0 if success, errno on failure.
*/
int bt_micp_mic_dev_register(struct bt_micp_mic_dev_register_param *param);
int bt_micp_mic_dev_register_safe(struct bt_micp_mic_dev_register_param *param);
/**
@@ -591,6 +591,7 @@ struct bt_tbs_register_param {
* @kconfig{CONFIG_BT_TBS_BEARER_COUNT})
* @retval -ENOEXEC The service failed to be registered
*/
int bt_tbs_register_bearer(const struct bt_tbs_register_param *param);
int bt_tbs_register_bearer_safe(const struct bt_tbs_register_param *param);
/**
@@ -611,6 +612,7 @@ int bt_tbs_register_bearer_safe(const struct bt_tbs_register_param *param);
* registered.
* @retval -ENOEXEC The service failed to be unregistered
*/
int bt_tbs_unregister_bearer(uint8_t bearer_index);
int bt_tbs_unregister_bearer_safe(uint8_t bearer_index);
/** @brief Prints all calls of all services to the debug log */
@@ -101,6 +101,7 @@ struct bt_tmap_cb {
*
* @return 0 on success or negative error value on failure.
*/
int bt_tmap_register(enum bt_tmap_role role);
int bt_tmap_register_safe(enum bt_tmap_role role);
/**
@@ -131,6 +131,7 @@ int bt_vcp_vol_rend_included_get_safe(struct bt_vcp_included *included);
*
* @return 0 if success, errno on failure.
*/
int bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param *param);
int bt_vcp_vol_rend_register_safe(struct bt_vcp_vol_rend_register_param *param);
/**
+1 -15
View File
@@ -44,6 +44,7 @@ list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/common/app/gap.c"
"${CMAKE_CURRENT_LIST_DIR}/host/common/app/gatt.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/crc/crc32_sw.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/assert.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/addr.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/bt_str.c"
"${CMAKE_CURRENT_LIST_DIR}/host/utils/buf.c"
@@ -57,21 +58,6 @@ list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_iso_common_api.c"
)
# L2CAP host shim wiring is currently only consumed by the OTS service
# (which lives in esp_ble_audio). common/l2cap.c has both host branches
# (Bluedroid returns -ENOTSUP), so compile it under either host whenever
# OTS is enabled; the NimBLE-only adapter glue stays NimBLE-gated.
if(CONFIG_BT_OTS OR CONFIG_BT_OTS_CLIENT)
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/common/l2cap.c"
)
if(CONFIG_BT_NIMBLE_ENABLED)
list(APPEND ble_iso_srcs
"${CMAKE_CURRENT_LIST_DIR}/host/adapter/nimble/l2cap.c"
)
endif()
endif()
list(APPEND ble_iso_include_dirs
"${CMAKE_CURRENT_LIST_DIR}/api/include"
"${CMAKE_CURRENT_LIST_DIR}/host/common/include"
@@ -35,15 +35,9 @@ esp_err_t esp_ble_iso_data_parse(const uint8_t ltv[], size_t size,
type = ltv[i + 1];
data_len = len - sizeof(uint8_t);
/* Skip empty value entries in strict parsing mode. */
if (data_len == 0) {
i += (size_t)len + 1;
continue;
}
/* A callback returning false aborts parsing; report it as an error so
* callers detect it (bt_audio_data_parse() returns -ECANCELED here). */
if (func(type, &ltv[i + 2], data_len, user_data) == false) {
/* Zero-length values are valid (e.g. BROADCAST_IMMEDIATE flag-only LTV).
* Match Zephyr bt_audio_data_parse: invoke callback with data=NULL. */
if (func(type, data_len > 0 ? &ltv[i + 2] : NULL, data_len, user_data) == false) {
return ESP_FAIL;
}
@@ -292,20 +286,26 @@ esp_err_t esp_ble_iso_big_sync(uint16_t sync_handle,
esp_ble_iso_big_sync_param_t *param,
esp_ble_iso_big_t **out_big)
{
esp_err_t ret = ESP_OK;
void *per_adv_sync;
int err;
per_adv_sync = bt_le_per_adv_sync_find_safe(sync_handle);
bt_le_host_lock();
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
return ESP_ERR_NOT_FOUND;
ret = ESP_ERR_NOT_FOUND;
goto unlock;
}
err = bt_iso_big_sync_safe(per_adv_sync, param, out_big);
err = bt_iso_big_sync(per_adv_sync, param, out_big);
if (err) {
return ESP_FAIL;
ret = ESP_FAIL;
}
return ESP_OK;
unlock:
bt_le_host_unlock();
return ret;
}
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
@@ -399,7 +399,7 @@ esp_err_t esp_ble_iso_chan_send_ts(esp_ble_iso_chan_t *chan,
}
#endif /* CONFIG_BT_ISO_TX */
void esp_ble_iso_gap_app_post_event(uint8_t type, void *param)
void esp_ble_iso_gap_app_post_event(uint16_t type, void *param)
{
bt_le_gap_app_post_event(type, param);
}
@@ -583,7 +583,7 @@ typedef struct {
* @param type Event type.
* @param param Event parameters.
*/
void esp_ble_iso_gap_app_post_event(uint8_t type, void *param);
void esp_ble_iso_gap_app_post_event(uint16_t type, void *param);
/**
* @brief Initialize ISO common functionality.
@@ -154,7 +154,7 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
/* Only AUTH_CMPL reaches here from the application's
* esp_ble_gap_register_callback path. EXT_ADV_REPORT / PA_SYNC_ESTAB /
@@ -164,26 +164,17 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
switch (event) {
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
const esp_ble_auth_cmpl_t *a = &p->ble_security.auth_cmpl;
struct gatt_conn *gatt_conn;
uint8_t sec_level;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(a->addr_type, a->bd_addr, false);
if (gatt_conn == NULL) {
LOG_ERR("[B]UnknownDevForEnc");
free(qev);
return;
}
qev->type = BT_LE_GAP_APP_PARAM_SECURITY_CHANGE;
qev->security_change.status = (a->success ? 0x00 : 0xFF);
/* Populate connection identity unconditionally so failure events
* carry valid conn_handle / role / dst to the application. */
qev->security_change.conn_handle = gatt_conn->conn_handle;
qev->security_change.role = gatt_conn->role;
qev->security_change.dst.type = gatt_conn->peer.type;
memcpy(qev->security_change.dst.val, gatt_conn->peer.val, BT_ADDR_SIZE);
/* dst only: conn_handle/role are resolved from it by the iso_task handler.
* Looking them up here would mean reading gatt_conns[] from BTC, which
* iso_task mutates under host_lock - and BTC must not take that lock. */
qev->security_change.dst.type = a->addr_type;
memcpy(qev->security_change.dst.val, a->bd_addr, BT_ADDR_SIZE);
if (qev->security_change.status == 0) {
/* Derive level from auth_mode bits (mirrors NimBLE's sec_state
@@ -204,9 +195,9 @@ void bt_le_bluedroid_gap_post_event(uint16_t event, void *param)
/* 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);
LOG_INF("[B]LtkFromStore");
} else {
LOG_WRN("[B]NoLtkForEnc[%u]", qev->security_change.conn_handle);
LOG_WRN("[B]NoLtkForEnc");
}
}
@@ -234,7 +225,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
switch (event) {
#if (BLE_50_EXTEND_SCAN_EN == TRUE)
@@ -258,7 +249,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
if (qev->ext_scan_recv.data_len) {
qev->ext_scan_recv.data = bt_le_ext_calloc(1, qev->ext_scan_recv.data_len);
assert(qev->ext_scan_recv.data);
BT_LE_ASSERT(qev->ext_scan_recv.data);
memcpy(qev->ext_scan_recv.data, r->adv_data, qev->ext_scan_recv.data_len);
}
break;
@@ -347,7 +338,7 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
if (qev->pa_sync_recv.data_len) {
qev->pa_sync_recv.data = bt_le_ext_calloc(1, qev->pa_sync_recv.data_len);
assert(qev->pa_sync_recv.data);
BT_LE_ASSERT(qev->pa_sync_recv.data);
memcpy(qev->pa_sync_recv.data, r->data, qev->pa_sync_recv.data_len);
}
break;
@@ -357,16 +348,6 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
#if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE)
case BTM_BLE_GAP_PERIODIC_ADV_SYNC_TRANS_RECV_EVT: {
const tBTM_BLE_PERIOD_ADV_SYNC_TRANS_RECV *r = &params->past_recv;
struct gatt_conn *gatt_conn;
/* Look up the PAST-delivering ACL handle by peer BDA — past_recv
* doesn't carry addr type. */
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0, r->addr, true);
if (gatt_conn == NULL) {
LOG_ERR("[B]UnknownPastSrc");
free(qev);
return;
}
qev->type = BT_LE_GAP_APP_PARAM_PA_SYNC_PAST;
@@ -378,7 +359,13 @@ static void bt_le_bluedroid_gap_post_event_bta(tBTA_DM_BLE_5_GAP_EVENT event,
qev->pa_sync_past.adv_phy = r->adv_phy;
qev->pa_sync_past.per_adv_itvl = r->adv_interval;
qev->pa_sync_past.adv_ca = r->adv_clk_accuracy;
qev->pa_sync_past.conn_handle = gatt_conn->conn_handle;
/* Sender identity: HCI reports it as a Connection_Handle and carries no
* address for it (adv_addr_type/adv_addr above are the advertiser's).
* btu_hcif copies the BDA out of the LCB but drops both the handle and
* the address type, so .type stays unset and the handler matches on the
* value alone. */
memcpy(qev->pa_sync_past.src_addr.val, r->addr, BT_ADDR_SIZE);
#if (BLE_50_EXTEND_SYNC_EN == TRUE)
/* PAST-established syncs need tracker too: a later app-initiated
@@ -21,6 +21,7 @@
#include "bta/bta_gatt_common.h"
#include "bta_gattc_int.h"
#include "btc_gatt_util.h"
#include "osi/allocator.h"
#include "stack/btm_ble_api.h"
#include "common/host.h"
@@ -148,7 +149,7 @@ static int gatts_indicate_resolve(const struct bt_gatt_attr *attr_in,
handle = data.handle;
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
assert(data.attr->user_data);
BT_LE_ASSERT(data.attr->user_data);
chrc = data.attr->user_data;
if ((chrc->properties & BT_GATT_CHRC_INDICATE) == 0) {
@@ -181,7 +182,7 @@ static struct gatts_list_node *gatts_list_node_alloc(struct bt_gatt_indicate_par
n->data_copy = NULL;
if (ip->len > 0) {
assert(ip->data);
BT_LE_ASSERT(ip->data);
n->data_copy = bt_le_ext_malloc(ip->len);
if (n->data_copy == NULL) {
@@ -226,6 +227,27 @@ static void gatts_notify_list_drain(struct gatt_conn *gatt_conn)
}
}
/* Remove the first notify marker whose value_handle matches. Used by
* handle_gatts_notify_tx_event so an indication CONF cannot be mistaken for
* a notify CONF when a notify marker is already queued (BTU/ISO race). */
static struct gatts_notify_node *
gatts_notify_take_by_handle(struct gatt_conn *gatt_conn, uint16_t handle)
{
struct gatts_notify_node *notify;
struct gatts_notify_node *tmp;
sys_snode_t *prev = NULL;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&gatt_conn->gatts_notify_list, notify, tmp, node) {
if (notify->value_handle == handle) {
sys_slist_remove(&gatt_conn->gatts_notify_list, prev, &notify->node);
return notify;
}
prev = &notify->node;
}
return NULL;
}
/* 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)
@@ -280,6 +302,13 @@ uint8_t bt_le_bluedroid_gattc_get_if(void)
uint8_t bt_le_bluedroid_gatts_get_if(void)
{
/* Callers pass this straight to BTA without checking, so the value matters
* less than the log: a silent 0 surfaces only as an unexplained sem timeout. */
if (gatts_if == 0) {
LOG_WRN("[B]GetGattsIfBeforeInit");
return 0xFF; /* ESP_GATT_IF_NONE */
}
return gatts_if;
}
@@ -435,7 +464,7 @@ static void gattc_connect_event_handler(tBTA_GATTC_CONNECT *connect)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_CONNECT_EVENT;
@@ -458,7 +487,7 @@ static void gattc_disconnect_event_handler(tBTA_GATTC_DISCONNECT *disconnect)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_DISCONNECT_EVENT;
@@ -478,7 +507,7 @@ static void gattc_open_event_handler(tBTA_GATTC_OPEN *open)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_OPEN_EVENT;
@@ -498,7 +527,7 @@ static void gattc_mtu_event_handler(tBTA_GATTC_CFG_MTU *cfg_mtu)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_MTU_EVENT;
@@ -519,7 +548,7 @@ static void gattc_disc_cmpl_event_handler(tBTA_GATTC_DIS_CMPL *disc_cmpl)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_DISC_CMPL_EVENT;
@@ -539,7 +568,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_READ_CHRC_EVENT;
@@ -553,7 +582,7 @@ static void gattc_read_chrc_event_handler(tBTA_GATTC_READ *read)
qev->gattc_read_chrc.len = read->p_value->len;
qev->gattc_read_chrc.value = bt_le_ext_calloc(1, read->p_value->len);
assert(qev->gattc_read_chrc.value);
BT_LE_ASSERT(qev->gattc_read_chrc.value);
memcpy(qev->gattc_read_chrc.value, read->p_value->p_value, read->p_value->len);
}
@@ -574,7 +603,7 @@ static void gattc_write_chrc_event_handler(tBTA_GATTC_WRITE *write)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_WRITE_CHRC_EVENT;
@@ -596,15 +625,15 @@ static void gatts_notify_tx_event_handler(tBTA_GATTS_REQ *req)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_NOTIFY_TX_EVENT;
/* BTA fires CONF_EVT for both indication acks and immediate notify
* completions; tBTA_GATTS_REQ carries no flag to tell them apart. The
* bluedroid handler disambiguates by popping a parallel notify marker
* list first (see gatts_notify_enqueue / handle_gatts_notify_tx_event),
* so is_notify here is left unused on this path. */
* bluedroid handler matches by attr_handle against gatts_list head /
* gatts_notify_list markers (see gatts_notify_take_by_handle), so
* is_notify here is left unused on this path. */
qev->gatts_notify_tx.is_notify = false;
qev->gatts_notify_tx.conn_id = req->conn_id;
qev->gatts_notify_tx.attr_handle = req->handle;
@@ -623,7 +652,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_NOTIFY_RX_EVENT;
@@ -635,7 +664,7 @@ static void gattc_notify_rx_event_handler(tBTA_GATTC_NOTIFY *notify)
qev->gattc_notify_rx.len = notify->len;
qev->gattc_notify_rx.value = bt_le_ext_calloc(1, notify->len);
assert(qev->gattc_notify_rx.value);
BT_LE_ASSERT(qev->gattc_notify_rx.value);
memcpy(qev->gattc_notify_rx.value, notify->value, notify->len);
}
@@ -656,7 +685,7 @@ static void gatts_connect_event_handler(tBTA_GATTS_CONN *connect)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_CONNECT_EVENT;
@@ -679,7 +708,7 @@ static void gatts_disconnect_event_handler(tBTA_GATTS_CONN *disconnect)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_DISCONNECT_EVENT;
@@ -700,7 +729,7 @@ static void gatts_mtu_event_handler(tBTA_GATTS_REQ *req)
/* req->p_data is non-NULL here: BTA always passes a stack object. */
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_MTU_EVENT;
@@ -720,7 +749,7 @@ static void gatts_read_req_handler(tBTA_GATTS_REQ *req)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_READ_EVENT;
@@ -746,7 +775,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
/* req->p_data is non-NULL here: BTA always passes a stack object. */
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_WRITE_EVENT;
@@ -762,7 +791,7 @@ static void gatts_write_req_handler(tBTA_GATTS_REQ *req)
qev->gatts_write.len = req->p_data->write_req.len;
qev->gatts_write.value = bt_le_ext_calloc(1, req->p_data->write_req.len);
assert(qev->gatts_write.value);
BT_LE_ASSERT(qev->gatts_write.value);
memcpy(qev->gatts_write.value, req->p_data->write_req.value, req->p_data->write_req.len);
}
@@ -783,7 +812,7 @@ static void gatts_exec_write_req_handler(tBTA_GATTS_REQ *req)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTS_EXEC_WRITE_EVENT;
qev->gatts_exec_write.conn_id = req->conn_id;
@@ -1369,7 +1398,7 @@ static void post_acl_connect_app_event(struct gatt_conn *gatt_conn)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GAP_APP_PARAM_ACL_CONNECT;
@@ -1397,7 +1426,7 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GAP_APP_PARAM_ACL_DISCONNECT;
@@ -1411,6 +1440,24 @@ static void post_acl_disconnect_app_event(uint16_t conn_handle, uint8_t reason)
}
}
/* Fire func(err) for every pending GATTS indication and free the nodes, so lib
* state machines waiting on a confirm unblock. conn may be NULL (already gone)
* — func(NULL, ...) still releases the waiter. */
static void gatts_list_fire_and_drain(struct gatt_conn *gatt_conn,
struct bt_conn *conn, uint8_t err)
{
struct gatts_list_node *n;
sys_snode_t *snode;
while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) {
n = CONTAINER_OF(snode, struct gatts_list_node, node);
if (n->params_copy.func) {
n->params_copy.func(conn, &n->params_copy, err);
}
gatts_list_node_free(n);
}
}
static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
{
struct gatt_conn *gatt_conn;
@@ -1423,8 +1470,22 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event->peer.type, event->peer.val, false);
if (gatt_conn) {
LOG_ERR("[B]DevAlreadyExists");
return;
if (gatt_conn->conn_handle == event->conn_handle) {
LOG_ERR("[B]DevAlreadyExists");
return;
}
/* Same peer, different handle: prior disconnect was missed/delayed.
* Drop the stale slot so reconnect can proceed. */
LOG_WRN("[B]StaleGattcConn[%u][%u]", gatt_conn->conn_handle, event->conn_handle);
post_acl_disconnect_app_event(gatt_conn->conn_handle, HCI_ERR_UNSPECIFIED);
/* Stale slot never gets a disconnect event: fire pending indication
* funcs so lib state machines unblock before the drain in reset. */
gatts_list_fire_and_drain(gatt_conn, NULL, BT_ATT_ERR_UNLIKELY);
reset_gatt_conn(gatt_conn);
}
/* App initiated via esp_ble_gattc_aux_open(engine_gattc_if, ...). BTA
@@ -1450,9 +1511,7 @@ static void handle_gattc_connect_event(struct bt_le_gattc_connect_event *event)
static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *event)
{
struct gatt_conn *gatt_conn;
struct gatts_list_node *n;
struct bt_conn *conn;
sys_snode_t *snode;
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
if (gatt_conn == NULL) {
@@ -1463,22 +1522,15 @@ static void handle_gattc_disconnect_event(struct bt_le_gattc_disconnect_event *e
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
* pending indication so lib state machines don't stall. */
conn = bt_le_acl_conn_find(event->conn_handle);
while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) {
n = CONTAINER_OF(snode, struct gatts_list_node, node);
if (conn && n->params_copy.func) {
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
}
gatts_list_node_free(n);
}
/* CENTRAL may also run a GATT server (e.g. CAP Initiator with PACS):
* fire func(err) for pending indications so lib state machines don't
* stall. conn may be gone — func(NULL, ...) still unblocks waiters. */
gatts_list_fire_and_drain(gatt_conn, conn, BT_ATT_ERR_UNLIKELY);
post_acl_disconnect_app_event(gatt_conn->conn_handle, event->reason);
/* Reset the corresponding gatt_conn */
reset_gatt_conn(gatt_conn);
}
}
@@ -1502,13 +1554,11 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
post_acl_connect_app_event(gatt_conn);
if (gatt_conn->status) {
/* If failed to create connection, reset the corresponding gatt_conn */
reset_gatt_conn(gatt_conn);
return;
}
} else {
if (gatt_conn->gattc_open && gatt_conn->status != BTA_GATT_OK) {
/* Failed to open gatt client, post an event to notify the app layer */
bt_le_gattc_app_open_event(event, gattc_if);
return;
}
@@ -1526,15 +1576,13 @@ static void handle_gattc_open_event(struct bt_le_gattc_open_event *event)
gatt_conn->cfg_mtu = 1;
}
/* Post an event to the app layer, we need this event to discover
* services and characteristics, etc.
*/
bt_le_gattc_app_open_event(event, gattc_if);
}
static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
{
struct gatt_conn *gatt_conn;
uint16_t prev_mtu;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
@@ -1548,6 +1596,8 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
* 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.
*/
prev_mtu = gatt_conn->mtu;
if (gatt_conn->mtu != 0) {
LOG_INF("[B]GattcMtuExchanged[%u][%u][%u]",
gatt_conn->conn_handle, gatt_conn->mtu, event->mtu);
@@ -1558,13 +1608,17 @@ static void handle_gattc_mtu_event(struct bt_le_gattc_mtu_event *event)
}
if (gatt_conn->cfg_mtu && gatt_conn->mtu_posted == 0) {
/* Post an event to the app layer, we need this event to discover
* services and characteristics, etc. Report the clamped value so
* the app stays consistent with gatt_conn state (mirrors NimBLE's
* BLE_GAP_EVENT_MTU which already carries the negotiated value). */
/* Report the clamped value so the app stays consistent with
* gatt_conn state (mirrors NimBLE's BLE_GAP_EVENT_MTU). */
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
gatt_conn->mtu_posted = 1; /* Mark MTU event as posted */
gatt_conn->mtu_posted = 1;
} else if (gatt_conn->mtu_posted && gatt_conn->mtu < prev_mtu) {
/* Dual-role: peer/side may clamp after the first post; app must
* learn the smaller value or it will oversize ATT PDUs. */
LOG_INF("[B]GattcMtuClamped[%u][%u][%u]",
event->conn_handle, prev_mtu, gatt_conn->mtu);
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
}
}
@@ -1617,7 +1671,6 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
const uint8_t *val;
sys_snode_t *snode;
uint16_t vlen;
uint16_t off;
uint8_t ret;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
@@ -1660,11 +1713,14 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
goto end;
}
if ((read_copy.handle_count == 0 &&
(event->attr_handle < read_copy.by_uuid.start_handle ||
event->attr_handle > read_copy.by_uuid.end_handle)) ||
(read_copy.handle_count == 1 &&
event->attr_handle != read_copy.single.handle)) {
/* On error BTA often returns attr_handle 0; only validate the handle when
* the read succeeded, otherwise pass the real ATT status to the caller. */
if (event->status == 0 &&
((read_copy.handle_count == 0 &&
(event->attr_handle < read_copy.by_uuid.start_handle ||
event->attr_handle > read_copy.by_uuid.end_handle)) ||
(read_copy.handle_count == 1 &&
event->attr_handle != read_copy.single.handle))) {
LOG_ERR("[B]GattcRdCharInvRsp[%u][%u][%u][%u][%u]",
read_copy.handle_count, event->attr_handle,
read_copy.by_uuid.start_handle,
@@ -1676,13 +1732,6 @@ static void handle_gattc_read_chrc_event(struct bt_le_gattc_read_chrc_event *eve
val = event->value;
vlen = event->len;
if (read_copy.handle_count == 1 && read_copy.single.offset != 0 &&
event->status == 0) {
off = read_copy.single.offset;
val = (off < event->len) ? event->value + off : NULL;
vlen = (off < event->len) ? (event->len - off) : 0;
}
/* 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) {
@@ -1745,7 +1794,7 @@ static void handle_gattc_write_chrc_event(struct bt_le_gattc_write_chrc_event *e
params = op->write_params;
free(op);
assert(params && params->func);
BT_LE_ASSERT(params && params->func);
if (event->attr_handle != params->handle) {
LOG_ERR("[B]GattcWrCharInvRsp[%u][%u]", event->attr_handle, params->handle);
@@ -1826,8 +1875,22 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event)
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event->peer.type, event->peer.val, false);
if (gatt_conn) {
LOG_ERR("[B]DevAlreadyExists");
return;
if (gatt_conn->conn_handle == event->conn_handle) {
LOG_ERR("[B]DevAlreadyExists");
return;
}
/* Same peer, different handle: prior disconnect was missed/delayed.
* Drop the stale slot so reconnect can proceed. */
LOG_WRN("[B]StaleGattsConn[%u][%u]", gatt_conn->conn_handle, event->conn_handle);
post_acl_disconnect_app_event(gatt_conn->conn_handle, HCI_ERR_UNSPECIFIED);
/* Stale slot never gets a disconnect event: fire pending indication
* funcs so lib state machines unblock before the drain in reset. */
gatts_list_fire_and_drain(gatt_conn, NULL, BT_ATT_ERR_UNLIKELY);
reset_gatt_conn(gatt_conn);
}
gatt_conn = bt_le_bluedroid_find_free_gatt_conn();
@@ -1860,10 +1923,8 @@ static void handle_gatts_connect_event(struct bt_le_gatts_connect_event *event)
static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *event)
{
struct gatts_list_node *n;
struct gatt_conn *gatt_conn;
struct bt_conn *conn;
sys_snode_t *snode;
gatt_conn = find_gatt_conn_with_conn_id(event->conn_id);
if (gatt_conn == NULL) {
@@ -1879,32 +1940,22 @@ static void handle_gatts_disconnect_event(struct bt_le_gatts_disconnect_event *e
return;
}
/* conn may already have been torn down by the ACL layer before this event
* arrives — best-effort lookup, NULL is tolerated and the drain still runs
* (params->func is just skipped for nodes we can't address). */
conn = bt_le_acl_conn_find(event->conn_handle);
/* Fire func with err for every pending indication. Mirrors NimBLE stack's
* ble_gatts_indicate_fail_notconn on disconn — BTA never delivers CONF_EVT
* for in-flight indications when the conn drops, so the adapter has to
* simulate that fail path to keep lib state machines from stalling. */
while ((snode = sys_slist_get(&gatt_conn->gatts_list)) != NULL) {
n = CONTAINER_OF(snode, struct gatts_list_node, node);
if (conn && n->params_copy.func) {
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
}
gatts_list_node_free(n);
}
/* conn may be gone — fire func(NULL, err) for pending indications so upper
* layers don't stall waiting for a CONF_EVT BTA never delivers after the
* link drops. */
gatts_list_fire_and_drain(gatt_conn, conn, BT_ATT_ERR_UNLIKELY);
post_acl_disconnect_app_event(gatt_conn->conn_handle, event->reason);
/* Reset the corresponding gatt_conn */
reset_gatt_conn(gatt_conn);
}
static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
{
struct gatt_conn *gatt_conn;
uint16_t prev_mtu;
gatt_conn = find_gatt_conn_by_conn_id_or_index(event->conn_id);
if (gatt_conn == NULL) {
@@ -1918,6 +1969,8 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
* 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.
*/
prev_mtu = gatt_conn->mtu;
if (gatt_conn->mtu != 0) {
LOG_INF("[B]GattsMtuExchanged[%u][%u][%u]",
gatt_conn->conn_handle, gatt_conn->mtu, event->mtu);
@@ -1931,7 +1984,12 @@ static void handle_gatts_mtu_event(struct bt_le_gatts_mtu_event *event)
/* Report clamped value — see handle_gattc_mtu_event. */
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
gatt_conn->mtu_posted = 1; /* Mark MTU event as posted */
gatt_conn->mtu_posted = 1;
} else if (gatt_conn->mtu < prev_mtu) {
/* Dual-role clamp after first post — see handle_gattc_mtu_event. */
LOG_INF("[B]GattsMtuClamped[%u][%u][%u]",
event->conn_handle, prev_mtu, gatt_conn->mtu);
bt_le_gatt_app_mtu_change_event(event->conn_handle, gatt_conn->mtu);
}
}
@@ -1969,11 +2027,11 @@ static void handle_gatts_read_event(struct bt_le_gatts_read_event *event)
goto end;
}
/* The tBTA_GATT_STATUS structure is too large, hence use
/* The tBTA_GATTS_RSP structure is too large, hence use
* dynamic memory here to avoid stack overflow.
*/
rsp = bt_le_ext_calloc(1, sizeof(*(rsp)));
assert(rsp);
BT_LE_ASSERT(rsp);
rsp->attr_value.handle = event->attr_handle;
@@ -2069,7 +2127,7 @@ static void handle_gatts_prepare_write(struct bt_le_gatts_write_event *event)
if (status == BTA_GATT_OK) {
/* The prepare-write response must echo handle/offset/value. */
rsp = bt_le_ext_calloc(1, sizeof(*rsp));
assert(rsp);
BT_LE_ASSERT(rsp);
rsp->attr_value.handle = event->attr_handle;
rsp->attr_value.offset = event->offset;
@@ -2133,7 +2191,6 @@ static void handle_gatts_write_event(struct bt_le_gatts_write_event *event)
goto end;
}
/* Check if the attribute UUID is CCCD */
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) == 0) {
if (event->len != 2) {
LOG_ERR("[B]GattsWrInvPdu[%u]", event->len);
@@ -2256,10 +2313,12 @@ static void handle_gatts_exec_write_event(struct bt_le_gatts_exec_write_event *e
static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *event)
{
struct gatts_list_node *n;
struct gatts_notify_node *notify;
struct gatt_conn *gatt_conn;
struct gatts_list_node *n;
struct bt_conn *conn;
sys_snode_t *snode;
bool ind_match;
/* 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
@@ -2278,28 +2337,51 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve
return;
}
/* Notify CONF_EVT first: BTA fires CONF_EVT immediately after send for
* notifications; pop the matching marker so we don't mistake it for an
* indication ack and corrupt the indication queue. */
snode = sys_slist_get(&gatt_conn->gatts_notify_list);
/* BTA fires CONF_EVT for both notify completions and indication acks,
* with no type flag. Match by attr_handle — do NOT assume "any notify
* marker means this CONF is a notify": a notify may be enqueued on the
* ISO path while an earlier indication's peer ACK is already queued,
* which would swallow the indication CONF and stall gatts_list. When
* both heads share the same handle, prefer indication (peer ACK); the
* notify CONF arrives shortly and claims the leftover marker. */
snode = sys_slist_peek_head(&gatt_conn->gatts_list);
ind_match = false;
n = NULL;
if (snode != NULL) {
struct gatts_notify_node *nn = CONTAINER_OF(snode, struct gatts_notify_node, node);
n = CONTAINER_OF(snode, struct gatts_list_node, node);
ind_match = (n->value_handle == event->attr_handle);
}
if (nn->value_handle != event->attr_handle) {
LOG_DBG("[B]NotifyTxHdlSkew[%u][%u]", event->attr_handle, nn->value_handle);
if (!ind_match) {
notify = gatts_notify_take_by_handle(gatt_conn, event->attr_handle);
if (notify != NULL) {
free(notify);
return;
}
free(nn);
if (n == NULL) {
LOG_WRN("[B]NotifyTxNoPending[%u][%u]",
event->conn_handle, event->attr_handle);
return;
}
/* Indication head exists but handle disagrees, and no notify marker
* matched either — list is out of sync. Drop the head to keep
* advancing (alternative is freezing the queue). */
LOG_ERR("[B]NotifyTxHdlMismatch[%u][%u]", event->attr_handle, n->value_handle);
(void)sys_slist_get(&gatt_conn->gatts_list);
if (n->params_copy.func) {
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
}
gatts_list_node_free(n);
return;
}
/* No notify pending — this CONF_EVT is an indication ack. */
snode = sys_slist_get(&gatt_conn->gatts_list);
if (snode == NULL) {
LOG_WRN("[B]NotifyTxNoPending[%u]", event->conn_handle);
return;
}
n = CONTAINER_OF(snode, struct gatts_list_node, node);
/* Indication ack for the current head. */
(void)sys_slist_get(&gatt_conn->gatts_list);
/* Submit next pending BEFORE firing the completion cb. host_mutex is
* recursive, so a cb that re-enters bt_gatt_indicate isn't blocked;
@@ -2319,20 +2401,6 @@ static void handle_gatts_notify_tx_event(struct bt_le_gatts_notify_tx_event *eve
}
}
if (n->value_handle != event->attr_handle) {
LOG_ERR("[B]NotifyTxHdlMismatch[%u][%u]", event->attr_handle, n->value_handle);
/* Mismatch is fatal here: BTA delivers CONF_EVT in FIFO order, so a
* head-vs-event handle disagreement means our list is out of sync.
* Drop the head to keep advancing (alternative is freezing the queue).
* Notify lib with err so its state machine doesn't stall waiting
* for a callback that will never match. */
if (n->params_copy.func) {
n->params_copy.func(conn, &n->params_copy, BT_ATT_ERR_UNLIKELY);
}
gatts_list_node_free(n);
return;
}
if (n->params_copy.func) {
n->params_copy.func(conn, &n->params_copy, event->status);
}
@@ -2410,7 +2478,7 @@ void bt_le_bluedroid_gatt_handle_event(uint8_t *data, size_t data_len)
break;
default:
assert(0);
BT_LE_ASSERT(0);
break;
}
@@ -2428,7 +2496,7 @@ void bt_le_bluedroid_gatts_svc_cb_register(struct gatts_svc_cb *cb)
void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid_out)
{
assert(uuid_out && uuid_in);
BT_LE_ASSERT(uuid_out && uuid_in);
if (uuid_in->type == BT_UUID_TYPE_16) {
((tBT_UUID *)uuid_out)->len = LEN_UUID_16;
@@ -2440,7 +2508,7 @@ void bt_le_bluedroid_gatt_uuid_convert(const struct bt_uuid *uuid_in, void *uuid
((tBT_UUID *)uuid_out)->len = LEN_UUID_128;
memcpy(((tBT_UUID *)uuid_out)->uu.uuid128, BT_UUID_128(uuid_in)->val, LEN_UUID_128);
} else {
assert(0);
BT_LE_ASSERT(0);
}
}
@@ -2526,8 +2594,8 @@ ssize_t bt_le_bluedroid_gatts_attr_read(struct bt_conn *conn, const struct bt_ga
return len;
}
/* Append a notify marker so handle_gatts_notify_tx_event can pop it off the
* notify list ahead of any pending indication head. Returns -ENOTCONN when
/* Append a notify marker so handle_gatts_notify_tx_event can match CONF_EVT
* by attr_handle against pending notifications. Returns -ENOTCONN when
* the gatt_conn is already torn down (caller should skip the BTA send to
* avoid a "Unknown connection ID" log from BTA), -ENOMEM on alloc fail. */
static int gatts_notify_enqueue(struct bt_conn *conn, uint16_t value_handle)
@@ -2582,7 +2650,6 @@ static int gatts_notify(struct bt_conn *conn,
data.attr = *attr;
data.handle = bt_gatt_attr_get_handle(data.attr);
/* Lookup UUID if it was given */
if (uuid) {
if (bt_gatts_find_attr_by_uuid(&data, uuid) == false) {
return -ENOENT;
@@ -2595,12 +2662,11 @@ static int gatts_notify(struct bt_conn *conn,
}
}
/* Check if attribute is a characteristic then adjust the handle */
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
struct bt_gatt_chrc *chrc;
uint16_t required;
assert(data.attr->user_data);
BT_LE_ASSERT(data.attr->user_data);
chrc = data.attr->user_data;
required = need_cfm ? BT_GATT_CHRC_INDICATE : BT_GATT_CHRC_NOTIFY;
@@ -2775,7 +2841,6 @@ int bt_le_bluedroid_gattc_disc_start(uint16_t conn_handle)
true, BTA_GATT_TRANSPORT_LE, false, 0,
false, 0xff, 0xff, 0, NULL, NULL, NULL);
/* Mark the gattc open as in progress */
gatt_conn->gattc_open = 1;
return 0;
@@ -2859,11 +2924,32 @@ static int gattc_disc_primary_svc(struct bt_conn *conn, uint16_t conn_id,
end:
if (db) {
free(db);
osi_free(db);
}
return 0;
}
/* BTA expands every DB UUID to 128-bit. Return true and write the 16-bit
* value only when it matches the Bluetooth Base UUID; otherwise unsupported. */
static bool bluedroid_db_uuid_as_16(const bt_uuid_t *src, uint16_t *out_val)
{
static const uint8_t base[16] = {
BT_UUID_128_ENCODE(0x00000000, 0x0000, 0x1000, 0x8000, 0x00805F9B34FB)
};
for (int i = 0; i < 16; i++) {
if (i == 12 || i == 13) {
continue;
}
if (src->uu[i] != base[i]) {
return false;
}
}
*out_val = sys_get_le16(&src->uu[12]);
return true;
}
static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
struct bt_gatt_discover_params *params)
{
@@ -2907,11 +2993,14 @@ static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
i, db[i].type, db[i].start_handle, db[i].end_handle,
db[i].attribute_handle, db[i].properties);
/* Only 16-bit Bluetooth Base UUIDs are supported; skip 32/128-bit
* rather than silently truncating them to a bogus 16-bit value. */
if (!bluedroid_db_uuid_as_16(&db[i].uuid, &svc_uuid.val)) {
LOG_WRN("[B]InclSvcNon16BitUuidSkip[%u]", db[i].attribute_handle);
continue;
}
svc_uuid.uuid.type = BT_UUID_TYPE_16;
/* The LSB 12-octets is Bluetooth_Base_UUID, and the remaining
* 2-octets or 4-octets is used by 16-bit or 32-bit UUID.
*/
svc_uuid.val = sys_get_le16(db[i].uuid.uu + 12);
inc_svc.uuid = &svc_uuid.uuid;
inc_svc.start_handle = db[i].start_handle;
@@ -2946,7 +3035,7 @@ static int gattc_disc_included_svc(struct bt_conn *conn, uint16_t conn_id,
end:
if (db) {
free(db);
osi_free(db);
}
return 0;
}
@@ -3041,7 +3130,7 @@ static int gattc_disc_chrc(struct bt_conn *conn, uint16_t conn_id,
end:
if (db) {
free(db);
osi_free(db);
}
return 0;
}
@@ -3058,10 +3147,10 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
uint16_t count = 0;
int err = 0;
assert(params);
assert(params->uuid);
assert(params->func);
assert(params->sub_params);
BT_LE_ASSERT(params);
BT_LE_ASSERT(params->uuid);
BT_LE_ASSERT(params->func);
BT_LE_ASSERT(params->sub_params);
/* Only descriptors can be filtered */
if (bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) == 0 ||
@@ -3100,6 +3189,9 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
db[0].type, db[0].start_handle, db[0].end_handle,
db[0].attribute_handle, db[0].properties);
/* Zephyr bt_gatt_attr contract: attr->uuid must be non-NULL. For
* filtered descriptor discovery this is the requested descriptor UUID. */
attr.uuid = params->uuid;
attr.handle = db[0].attribute_handle;
status = BTA_GATTC_RegisterForNotifications(gattc_if, conn->le.dst.a.val, chrc_handle);
@@ -3124,7 +3216,7 @@ static int gattc_disc_chrc_desc(struct bt_conn *conn, uint16_t conn_id,
end:
if (db) {
free(db);
osi_free(db);
}
return err;
}
@@ -3186,6 +3278,17 @@ int bt_le_bluedroid_gattc_read(struct bt_conn *conn, struct bt_gatt_read_params
return -ENODEV;
}
/* BTA cannot emit ATT Read Blob: bta_gattc_read hardcodes GATT_READ_BY_HANDLE
* and tBTA_GATTC_API_READ has no offset. Rejecting beats a Read Request from
* byte 0, which makes a continuation read stall silently. Do not hoist this
* into bt_gatt_read() - NimBLE has ble_gattc_read_long.
* TODO: drop once Bluedroid exposes a read-with-offset API. */
if (params->handle_count == 1 && params->single.offset != 0) {
LOG_ERR("[B]GattcRdNoBlob[%u][%u]",
params->single.handle, params->single.offset);
return -ENOTSUP;
}
/* BTA serializes via p_cmd_list (FIFO); we mirror with gattc_list so the
* EVT can recover the caller's params. Append before submit so the head
* is set when BTA delivers the cmpl synchronously on error paths. */
@@ -3230,6 +3333,13 @@ int bt_le_bluedroid_gattc_write(struct bt_conn *conn, struct bt_gatt_write_param
LOG_DBG("[B]GattcWr[%u]", conn->handle);
/* bta_gattc_write memcpy's into a stack tGATT_CL_COMPLETE sized
* GATT_MAX_ATTR_LEN, with no length check of its own */
if (params->length > BTA_GATT_MAX_ATTR_LEN) {
LOG_ERR("[B]GattcWrValTooBig[%u]", params->length);
return -EMSGSIZE;
}
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_handle(conn->handle);
if (gatt_conn == NULL) {
LOG_ERR("[B]NoConnInfo[%u]", conn->handle);
@@ -3261,6 +3371,12 @@ int bt_le_bluedroid_gattc_write_without_rsp(struct bt_conn *conn, uint16_t handl
LOG_DBG("[B]GattcWrCmd[%u][%u][%u]", conn->handle, handle, length);
/* Same BTU stack-overflow hazard as gattc_write */
if (length > BTA_GATT_MAX_ATTR_LEN) {
LOG_ERR("[B]GattcWrCmdValTooBig[%u]", length);
return -EMSGSIZE;
}
/* Mirror sibling GATT ops: refuse to forward doomed writes after
* disconnect cleared the gatt_conn slot (bt_conn->state may briefly
* still look CONNECTED during the teardown race). */
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct bt_l2cap_chan;
struct net_buf;
int bt_le_bluedroid_l2cap_chan_connect(uint16_t conn_handle);
int bt_le_bluedroid_l2cap_chan_disconnect(struct bt_l2cap_chan *chan);
int bt_le_bluedroid_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf);
void bt_le_bluedroid_l2cap_accept_result(uint16_t conn_handle, uint8_t l2cap_id,
uint16_t chan_handle, uint16_t result);
int bt_le_bluedroid_l2cap_init(void);
void bt_le_bluedroid_l2cap_deinit(void);
#ifdef __cplusplus
}
#endif
@@ -69,7 +69,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
uint16_t conn_handle;
tBTM_STATUS status;
assert(rsp);
BT_LE_ASSERT(rsp);
conn_handle = sys_get_le16(buf->data + 3);
@@ -89,7 +89,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
tx_sync.packet_seq_num = sys_get_le16(rsp_buf + 2);
tx_sync.tx_time_stamp = sys_get_le32(rsp_buf + 4);
tx_sync.time_offset = sys_get_le24(rsp_buf + 8) & 0xFFFFFF;
assert(tx_sync.conn_handle == conn_handle);
BT_LE_ASSERT(tx_sync.conn_handle == conn_handle);
}
#else /* USE_DIRECT_HCI */
bt_le_host_lock();
@@ -100,7 +100,7 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
LOG_ERR("[B]RdIsoTxSyncRspTimeout[0x%03x]", conn_handle);
status = BTM_ERR_PROCESSING;
} else {
assert(tx_sync.conn_handle == conn_handle);
BT_LE_ASSERT(tx_sync.conn_handle == conn_handle);
status = tx_sync.status;
}
bt_le_host_unlock();
@@ -134,7 +134,7 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
uint8_t cis_count;
uint8_t cig_id;
assert(rsp);
BT_LE_ASSERT(rsp);
cig_id = buf->data[3];
cis_count = buf->data[17];
@@ -162,8 +162,8 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
for (uint8_t i = 0; i < rsp_cis_count; i++) {
set_cig_params.cis_handle[i] = sys_get_le16(rsp_buf + 2 + i * 2);
}
assert(set_cig_params.cig_id == cig_id);
assert(set_cig_params.cis_count == cis_count);
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
}
#else /* USE_DIRECT_HCI */
struct ble_hci_le_cis_params *cis_params;
@@ -184,7 +184,7 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
mtl_p_to_c = sys_get_le16(buf->data + 15);
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params));
assert(cis_params);
BT_LE_ASSERT(cis_params);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params)];
@@ -213,8 +213,8 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
LOG_ERR("[B]SetCigParamsRspTimeout[%u]", cig_id);
status = BTM_ERR_PROCESSING;
} else {
assert(set_cig_params.cig_id == cig_id);
assert(set_cig_params.cis_count == cis_count);
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
status = set_cig_params.status;
if (status) {
LOG_ERR("[B]SetCigParamsCtrlFail[%u][%02x]", cig_id, status);
@@ -250,7 +250,7 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
uint8_t cis_count;
uint8_t cig_id;
assert(rsp);
BT_LE_ASSERT(rsp);
cig_id = buf->data[3];
cis_count = buf->data[17];
@@ -278,8 +278,8 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
for (uint8_t i = 0; i < rsp_cis_count; i++) {
set_cig_params.cis_handle[i] = sys_get_le16(rsp_buf + 2 + i * 2);
}
assert(set_cig_params.cig_id == cig_id);
assert(set_cig_params.cis_count == cis_count);
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
}
#else /* USE_DIRECT_HCI */
struct ble_hci_le_cis_params_test *cis_params;
@@ -302,7 +302,7 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
framing = buf->data[16];
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params_test));
assert(cis_params);
BT_LE_ASSERT(cis_params);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params_test)];
@@ -335,8 +335,8 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
LOG_ERR("[B]SetCigParamsTestRspTimeout[%u]", cig_id);
status = BTM_ERR_PROCESSING;
} else {
assert(set_cig_params.cig_id == cig_id);
assert(set_cig_params.cis_count == cis_count);
BT_LE_ASSERT(set_cig_params.cig_id == cig_id);
BT_LE_ASSERT(set_cig_params.cis_count == cis_count);
status = set_cig_params.status;
if (status) {
LOG_ERR("[B]SetCigParamsTestCtrlFail[%u][%02x]", cig_id, status);
@@ -377,7 +377,7 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp)
cis_count = buf->data[3];
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_cis_hdls));
assert(cis_params);
BT_LE_ASSERT(cis_params);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_hdl = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_cis_hdls));
@@ -629,7 +629,7 @@ static int hci_cmd_big_terminate_sync(struct net_buf *buf, struct net_buf **rsp)
uint8_t big_handle;
tBTM_STATUS status;
assert(rsp);
BT_LE_ASSERT(rsp);
big_handle = buf->data[3];
@@ -661,7 +661,7 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp
uint16_t conn_handle;
tBTM_STATUS status;
assert(rsp);
BT_LE_ASSERT(rsp);
conn_handle = sys_get_le16(buf->data + 3);
@@ -721,7 +721,7 @@ static int hci_cmd_remove_iso_data_path(struct net_buf *buf, struct net_buf **rs
uint16_t conn_handle;
tBTM_STATUS status;
assert(rsp);
BT_LE_ASSERT(rsp);
conn_handle = sys_get_le16(buf->data + 3);
@@ -859,7 +859,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + sizeof(ev);
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.status = 0x00;
ev.handle = params->btm_cis_disconnectd_evt.cis_handle;
@@ -876,7 +876,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev);
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.status = iso_hci_status(params->btm_cis_established_evt.status);
ev.conn_handle = params->btm_cis_established_evt.conn_handle;
@@ -907,7 +907,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev);
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.acl_handle = params->btm_cis_request_evt.acl_handle;
ev.cis_handle = params->btm_cis_request_evt.cis_handle;
@@ -926,7 +926,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev) + params->btm_big_cmpl.num_bis * 2;
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.status = iso_hci_status(params->btm_big_cmpl.status);
ev.big_handle = params->btm_big_cmpl.big_handle;
@@ -957,7 +957,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev);
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.big_handle = params->btm_big_term.big_handle;
ev.reason = params->btm_big_term.reason;
@@ -974,7 +974,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev) + params->btm_big_sync_estab.num_bis * 2;
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.status = iso_hci_status(params->btm_big_sync_estab.status);
ev.big_handle = params->btm_big_sync_estab.big_handle;
@@ -1003,7 +1003,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev);
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.big_handle = params->btm_big_sync_lost.big_handle;
ev.reason = params->btm_big_sync_lost.reason;
@@ -1020,7 +1020,7 @@ static void iso_evt_handler(tBTM_BLE_ISO_EVENT event, tBTM_BLE_ISO_CB_PARAMS *pa
qdata_len = 2 + 1 + sizeof(ev);
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
ev.sync_handle = params->btm_biginfo_report.sync_handle;
ev.num_bis = params->btm_biginfo_report.num_bis;
@@ -1275,7 +1275,7 @@ int bt_le_bluedroid_iso_init(void)
err = iso_enable_cis();
if (err) {
/* Roll back local init so a retry doesn't trip k_sem_create's
* assert(handle == NULL). Reverse order of init, skipping the
* BT_LE_ASSERT(handle == NULL). Reverse order of init, skipping the
* disable_cis step since enable never took effect. */
#if CONFIG_BT_ISO_RX
ble_host_register_rx_iso_data_cb(NULL);
@@ -83,7 +83,7 @@ void bt_le_nimble_gap_post_event(void *param)
int err;
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
memset(&desc, 0, sizeof(desc));
ev = param;
@@ -117,7 +117,7 @@ void bt_le_nimble_gap_post_event(void *param)
if (qev->ext_scan_recv.data_len) {
qev->ext_scan_recv.data = bt_le_ext_calloc(1, qev->ext_scan_recv.data_len);
assert(qev->ext_scan_recv.data);
BT_LE_ASSERT(qev->ext_scan_recv.data);
memcpy(qev->ext_scan_recv.data, ev->ext_disc.data, qev->ext_scan_recv.data_len);
}
@@ -172,7 +172,7 @@ void bt_le_nimble_gap_post_event(void *param)
if (qev->pa_sync_recv.data_len) {
qev->pa_sync_recv.data = bt_le_ext_calloc(1, qev->pa_sync_recv.data_len);
assert(qev->pa_sync_recv.data);
BT_LE_ASSERT(qev->pa_sync_recv.data);
memcpy(qev->pa_sync_recv.data, ev->periodic_report.data, qev->pa_sync_recv.data_len);
}
@@ -184,7 +184,7 @@ void bt_le_nimble_gap_post_event(void *param)
qev->acl_connect.status = ev->connect.status;
if (qev->acl_connect.status == 0) {
err = ble_gap_conn_find(ev->connect.conn_handle, &desc);
assert(err == 0);
BT_LE_ASSERT(err == 0);
qev->acl_connect.conn_handle = desc.conn_handle;
qev->acl_connect.role = desc.role;
@@ -206,7 +206,7 @@ void bt_le_nimble_gap_post_event(void *param)
qev->security_change.status = ev->enc_change.status;
if (qev->security_change.status == 0) {
err = ble_gap_conn_find(ev->enc_change.conn_handle, &desc);
assert(err == 0);
BT_LE_ASSERT(err == 0);
qev->security_change.conn_handle = ev->enc_change.conn_handle;
qev->security_change.role = desc.role;
@@ -325,9 +325,15 @@ int bt_le_nimble_scan_stop(void)
LOG_DBG("[N]ScanStop");
rc = ble_gap_disc_cancel();
if (rc) {
if (rc && rc != BLE_HS_EALREADY) {
LOG_ERR("[N]ScanStopFail[%d]", rc);
}
/* EALREADY (not scanning, e.g. after privacy preemption): treat as success
* so bt_le_scan_stop clears a stale BT_DEV_SCANNING instead of locking out. */
if (rc == BLE_HS_EALREADY) {
rc = 0;
}
return nimble_err_to_errno(rc);
}
@@ -45,7 +45,7 @@ void bt_le_nimble_gatt_post_event(void *param)
}
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
switch (ev->type) {
case BLE_GAP_EVENT_MTU:
@@ -79,7 +79,7 @@ void bt_le_nimble_gatt_post_event(void *param)
qev->gattc_notify_rx.len = total_len;
qev->gattc_notify_rx.value = bt_le_ext_calloc(1, total_len);
assert(qev->gattc_notify_rx.value);
BT_LE_ASSERT(qev->gattc_notify_rx.value);
os_mbuf_copydata(ev->notify_rx.om, 0, total_len, qev->gattc_notify_rx.value);
}
@@ -129,7 +129,7 @@ int bt_le_nimble_gatt_post_disc_event(uint16_t conn_handle, ble_uuid16_t *uuid,
}
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_DISCOVER_EVENT;
@@ -156,7 +156,7 @@ int bt_le_nimble_gatt_post_disc_cmpl_event(uint16_t conn_handle, uint8_t status)
LOG_DBG("[N]GattcDiscCmplEvtHdlr[%u][%02x]", conn_handle, status);
qev = bt_le_ext_calloc(1, sizeof(*qev));
assert(qev);
BT_LE_ASSERT(qev);
qev->type = BT_LE_GATTC_DISC_CMPL_EVENT;
@@ -299,7 +299,7 @@ void bt_le_nimble_gatt_handle_event(uint8_t *data, size_t data_len)
break;
default:
assert(0);
BT_LE_ASSERT(0);
break;
}
@@ -317,16 +317,19 @@ ssize_t bt_le_nimble_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_
void *buf, uint16_t buf_len, uint16_t offset,
const void *value, uint16_t value_len)
{
uint16_t len;
ssize_t len;
if (buf_len == UINT16_MAX) {
/* TODO: A better solution for using a variable for the cb pointer */
struct bt_le_nimble_gatt_read_cb *cb = buf;
assert(cb->read_cb);
BT_LE_ASSERT(cb->read_cb);
len = cb->read_cb(cb->read_arg, offset, value, value_len);
if (len < 0) {
return len;
}
} else {
assert(attr);
BT_LE_ASSERT(attr);
LOG_DBG("[N]Hdl[%u]", attr->handle);
@@ -339,7 +342,7 @@ ssize_t bt_le_nimble_gatts_attr_read(struct bt_conn *conn, const struct bt_gatt_
memcpy(buf, (uint8_t *)value + offset, len);
}
LOG_DBG("[N]GattsAttrRd[%u]", len);
LOG_DBG("[N]GattsAttrRd[%u]", (unsigned)len);
return len;
}
@@ -375,7 +378,7 @@ static int gatts_notify(struct bt_conn *conn, struct bt_gatt_notify_params *para
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
struct bt_gatt_chrc *chrc;
assert(data.attr->user_data);
BT_LE_ASSERT(data.attr->user_data);
chrc = data.attr->user_data;
if ((chrc->properties & BT_GATT_CHRC_NOTIFY) == 0) {
@@ -486,7 +489,7 @@ int bt_le_nimble_gattc_discover(struct bt_conn *conn, struct bt_gatt_discover_pa
return bt_le_nimble_gattc_db_disc_all_chrs(conn, params);
case BT_GATT_DISCOVER_DESCRIPTOR:
assert(params->uuid);
BT_LE_ASSERT(params->uuid);
/* Only descriptors can be filtered */
if (bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) == 0 ||
@@ -157,7 +157,7 @@ static void gattc_db_del(struct gattc_db *adb)
struct gattc_db_svc *asvc;
sys_snode_t *node;
assert(adb);
BT_LE_ASSERT(adb);
while ((node = sys_slist_get(&adb->svc_list)) != NULL) {
asvc = SYS_SLIST_CONTAINER(node, asvc, node);
@@ -182,7 +182,7 @@ static void gattc_db_svc_insert(struct gattc_db *adb, const struct ble_gatt_svc
struct gattc_db_svc *asvc;
asvc = bt_le_ext_calloc(1, sizeof(*asvc));
assert(asvc);
BT_LE_ASSERT(asvc);
memcpy(&asvc->svc, svc, sizeof(asvc->svc));
sys_slist_init(&asvc->inc_svc_list);
@@ -198,7 +198,7 @@ static void gattc_db_inc_svc_insert(struct gattc_db_svc *asvc, const struct ble_
struct gattc_db_inc_svc *ainc_svc;
ainc_svc = bt_le_ext_calloc(1, sizeof(*ainc_svc));
assert(ainc_svc);
BT_LE_ASSERT(ainc_svc);
memcpy(&ainc_svc->svc, inc_svc, sizeof(ainc_svc->svc));
sys_slist_init(&ainc_svc->chrc_list);
@@ -220,7 +220,7 @@ static void gattc_db_chrc_insert(sys_slist_t *chrc_list, const struct ble_gatt_c
struct gattc_db_chrc *achrc;
achrc = bt_le_ext_calloc(1, sizeof(*achrc));
assert(achrc);
BT_LE_ASSERT(achrc);
memcpy(&achrc->chrc, chrc, sizeof(achrc->chrc));
@@ -238,7 +238,10 @@ static void gattc_db_dsc_cccd_store(sys_slist_t *chrc_list,
/* LOG_DBG("[N]GattcDbDscCccdStore[%u]", chr_val_handle); */
SYS_SLIST_FOR_EACH_CONTAINER(chrc_list, achrc, node) {
if (achrc->chrc.val_handle == dsc->handle - 1) {
/* Match by the char value handle NimBLE reports this descriptor belongs
* to, not dsc->handle-1: a descriptor between value and CCCD would break
* the offset assumption and leave CCCD unstored. */
if (achrc->chrc.val_handle == chr_val_handle) {
if (achrc->cccd.handle) {
LOG_WRN("[N]GattcDbCccAlreadyUpd[%u][%u]", chr_val_handle, achrc->cccd.handle);
return;
@@ -582,12 +585,12 @@ static int gattc_db_disc_all_svcs_cb_safe(uint16_t conn_handle,
bt_le_host_lock();
adb = arg;
assert(adb);
assert(adb->conn_handle == conn_handle);
BT_LE_ASSERT(adb);
BT_LE_ASSERT(adb->conn_handle == conn_handle);
switch (error->status) {
case 0:
assert(svc);
BT_LE_ASSERT(svc);
LOG_DBG("[N]GattcDbDiscAllSvcs[0x%04x][%u][%u]",
svc->uuid.u16.value,
@@ -628,7 +631,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle,
int rc = 0;
asvc = arg;
assert(asvc);
BT_LE_ASSERT(asvc);
/* LOG_DBG("[N]GattcDbFindIncSvcsCb[%u][%04x]", conn_handle, error->status); */
@@ -643,7 +646,7 @@ static int gattc_db_find_inc_svcs_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(svc);
BT_LE_ASSERT(svc);
LOG_DBG("[N]GattcDbFindIncSvcs[0x%04x][%u][%u]",
svc->uuid.u16.value,
@@ -716,7 +719,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle,
int rc = 0;
ainc_svc = arg;
assert(ainc_svc);
BT_LE_ASSERT(ainc_svc);
/* LOG_DBG("[N]GattcDbDiscAllIncChrsCb[%u][%04x]", conn_handle, error->status); */
@@ -731,7 +734,7 @@ static int gattc_db_disc_all_inc_chrs_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(chrc);
BT_LE_ASSERT(chrc);
LOG_DBG("[N]GattcDbDiscAllIncChrs[%u][%u]", chrc->def_handle, chrc->val_handle);
@@ -774,7 +777,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle,
int rc = 0;
asvc = arg;
assert(asvc);
BT_LE_ASSERT(asvc);
/* LOG_DBG("[N]GattcDbDiscAllChrsCb[%u][%04x]", conn_handle, error->status); */
@@ -789,7 +792,7 @@ static int gattc_db_disc_all_chrs_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(chrc);
BT_LE_ASSERT(chrc);
LOG_DBG("[N]GattcDbDiscAllChrs[%u][%u]", chrc->def_handle, chrc->val_handle);
@@ -833,7 +836,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle,
int rc = 0;
ainc_svc = arg;
assert(ainc_svc);
BT_LE_ASSERT(ainc_svc);
LOG_DBG("[N]GattcDbDiscAllIncDscsCb[%u][%04x][%u]",
conn_handle, error->status, chr_val_handle);
@@ -849,7 +852,7 @@ static int gattc_db_disc_all_inc_dscs_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(dsc);
BT_LE_ASSERT(dsc);
if (dsc->uuid.u.type == BLE_UUID_TYPE_16 &&
dsc->uuid.u16.value == BT_UUID_GATT_CCC_VAL) {
@@ -896,7 +899,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle,
int rc = 0;
asvc = arg;
assert(asvc);
BT_LE_ASSERT(asvc);
/* LOG_DBG("[N]GattcDbDiscAllDscsCb[%u][%04x][%u]", */
/* conn_handle, error->status, chr_val_handle); */
@@ -912,7 +915,7 @@ static int gattc_db_disc_all_dscs_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(dsc);
BT_LE_ASSERT(dsc);
if (dsc->uuid.u.type == BLE_UUID_TYPE_16 &&
dsc->uuid.u16.value == BT_UUID_GATT_CCC_VAL) {
@@ -958,7 +961,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle,
int rc = 0;
achrc = arg;
assert(achrc);
BT_LE_ASSERT(achrc);
/* LOG_DBG("[N]GattcDbEnableNtfCb[%u][%04x]", conn_handle, error->status); */
@@ -973,7 +976,7 @@ static int gattc_db_enable_notify_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(attr);
BT_LE_ASSERT(attr);
LOG_DBG("[N]GattcDbEnableNotify[%u]", attr->handle);
break;
@@ -1135,13 +1138,18 @@ static uint8_t gattc_disc_chr_each(struct bt_conn *conn,
chrc_uuid.val = achrc->chrc.uuid.u16.value;
break;
default:
return BT_GATT_ITER_STOP;
/* Skip unsupported UUID types (e.g. 128-bit custom) instead of aborting
* the whole discovery; LE Audio only uses 16-bit characteristics. */
LOG_WRN("[N]GattcDbChrcSkipUuid[%u][%u][%u]",
conn->handle, achrc->chrc.uuid.u.type, achrc->chrc.val_handle);
return BT_GATT_ITER_CONTINUE;
}
chrc.uuid = &chrc_uuid.uuid;
chrc.value_handle = achrc->chrc.val_handle;
chrc.properties = achrc->chrc.properties;
attr.uuid = BT_UUID_GATT_CHRC; /* declaration attr UUID */
attr.user_data = &chrc;
attr.handle = achrc->chrc.def_handle;
@@ -1247,7 +1255,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event)
goto end;
}
assert(event->params);
BT_LE_ASSERT(event->params);
switch (event->type) {
case GATTC_DISC_TYPE_SVC_BY_UUID:
@@ -1273,7 +1281,7 @@ void handle_gattc_db_disc_event_safe(struct bt_le_gattc_discover_event *event)
break;
default:
assert(0);
BT_LE_ASSERT(0);
break;
}
@@ -1344,7 +1352,7 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn,
*/
sub_params = params->sub_params;
assert(sub_params);
BT_LE_ASSERT(sub_params);
LOG_DBG("[N]GattcDbHdlDiscAllDscs[%u][%u]", conn->handle, sub_params->value_handle);
@@ -1394,6 +1402,12 @@ static int handle_gattc_disc_all_dscs(struct bt_conn *conn,
}
end:
if (found == NULL && rc == 0) {
/* Char missing, or char found with cccd.handle==0 — not success.
* Returning 0 here made bt_gatt_subscribe() append a dead node. */
rc = -ENODEV;
}
params->func(conn, found, params);
return rc;
@@ -101,11 +101,11 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
int rc = 0;
read_params = arg;
assert(read_params);
assert(read_params->func);
assert(read_params->handle_count == 0);
assert(read_params->by_uuid.uuid);
assert(read_params->by_uuid.uuid->type == BT_UUID_TYPE_16);
BT_LE_ASSERT(read_params);
BT_LE_ASSERT(read_params->func);
BT_LE_ASSERT(read_params->handle_count == 0);
BT_LE_ASSERT(read_params->by_uuid.uuid);
BT_LE_ASSERT(read_params->by_uuid.uuid->type == BT_UUID_TYPE_16);
LOG_DBG("[N]GattcNrpRdByUuidCb[%u][%04x][%s]",
conn_handle, error->status, bt_uuid_str(read_params->by_uuid.uuid));
@@ -123,19 +123,19 @@ static int gattc_nrp_read_by_uuid_cb_safe(uint16_t conn_handle,
* events followed by EDONE, so the head must survive across events.
* It is removed only on terminal status (EDONE / error) below. */
nrp = gatt_nrp_find(conn->handle);
assert(nrp);
BT_LE_ASSERT(nrp);
node = sys_slist_peek_head(&nrp->list);
assert(node);
BT_LE_ASSERT(node);
nrp_node = CONTAINER_OF(node, struct gatt_nrp_node, node);
assert(nrp_node->type == GATTC_NRP_READ_BY_UUID);
assert(nrp_node->read_by_uuid.params == read_params);
BT_LE_ASSERT(nrp_node->type == GATTC_NRP_READ_BY_UUID);
BT_LE_ASSERT(nrp_node->read_by_uuid.params == read_params);
iter_stopped = nrp_node->read_by_uuid.iter_stopped;
switch (error->status) {
case 0:
assert(attr);
assert(attr->om);
BT_LE_ASSERT(attr);
BT_LE_ASSERT(attr->om);
LOG_DBG("[N]GattcNrpHdl[%u][%u][%u]Len[%u]",
attr->handle, read_params->by_uuid.start_handle,
@@ -198,10 +198,10 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
int rc = 0;
read_params = arg;
assert(read_params);
assert(read_params->func);
assert(read_params->handle_count == 1);
assert(read_params->single.offset != 0);
BT_LE_ASSERT(read_params);
BT_LE_ASSERT(read_params->func);
BT_LE_ASSERT(read_params->handle_count == 1);
BT_LE_ASSERT(read_params->single.offset != 0);
LOG_DBG("[N]GattcNrpRdLongCb[%u][%04x]", conn_handle, error->status);
@@ -217,19 +217,19 @@ static int gattc_nrp_read_long_cb_safe(uint16_t conn_handle,
/* Peek (don't pop) — per-fragment events may precede EDONE; head is
* removed only on terminal status below. */
nrp = gatt_nrp_find(conn->handle);
assert(nrp);
BT_LE_ASSERT(nrp);
node = sys_slist_peek_head(&nrp->list);
assert(node);
BT_LE_ASSERT(node);
nrp_node = CONTAINER_OF(node, struct gatt_nrp_node, node);
assert(nrp_node->type == GATTC_NRP_READ_LONG);
assert(nrp_node->read_long.params == read_params);
BT_LE_ASSERT(nrp_node->type == GATTC_NRP_READ_LONG);
BT_LE_ASSERT(nrp_node->read_long.params == read_params);
iter_stopped = nrp_node->read_long.iter_stopped;
switch (error->status) {
case 0:
assert(attr);
assert(attr->om);
BT_LE_ASSERT(attr);
BT_LE_ASSERT(attr->om);
LOG_DBG("[N]GattcNrpHdl[%u][%u]Offset[%u]Len[%u]",
attr->handle, read_params->single.handle, attr->offset, attr->om->om_len);
@@ -289,9 +289,9 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
original = nrp_node->read_single.params;
func = params_copy->func;
assert(func);
assert(params_copy->handle_count == 1);
assert(params_copy->single.offset == 0);
BT_LE_ASSERT(func);
BT_LE_ASSERT(params_copy->handle_count == 1);
BT_LE_ASSERT(params_copy->single.offset == 0);
LOG_DBG("[N]GattcNrpRdSingleCb[%u][%04x]", conn_handle, error->status);
@@ -309,8 +309,8 @@ static int gattc_nrp_read_single_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(attr);
assert(attr->om);
BT_LE_ASSERT(attr);
BT_LE_ASSERT(attr->om);
LOG_DBG("[N]GattcNrpHdl[%u][%u]Len[%u]",
attr->handle, original->single.handle, attr->om->om_len);
@@ -405,8 +405,8 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
int rc = 0;
write_params = arg;
assert(write_params);
assert(write_params->func);
BT_LE_ASSERT(write_params);
BT_LE_ASSERT(write_params->func);
LOG_DBG("[N]GattcNrpWrCb[%u][%04x]", conn_handle, error->status);
@@ -424,7 +424,7 @@ static int gattc_nrp_write_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(attr);
BT_LE_ASSERT(attr);
LOG_DBG("[N]GattcNrpHdl[%u][%u]", attr->handle, write_params->handle);
write_params->func(conn, 0, write_params);
@@ -483,7 +483,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle,
int rc = 0;
sub_params = arg;
assert(sub_params);
BT_LE_ASSERT(sub_params);
LOG_DBG("[N]GattcNrpSubCb[%u][%04x]", conn_handle, error->status);
@@ -501,7 +501,7 @@ static int gattc_nrp_subscribe_cb_safe(uint16_t conn_handle,
switch (error->status) {
case 0:
assert(attr);
BT_LE_ASSERT(attr);
LOG_DBG("[N]GattcNrpHdl[%u]", attr->handle);
#if 0
@@ -561,7 +561,7 @@ static int gattc_nrp_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_pa
void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
int16_t attr_handle,
uint8_t status)
int status)
{
struct bt_gatt_indicate_params params = {0};
struct bt_gatt_attr attr = {0};
@@ -604,13 +604,13 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para
struct os_mbuf *om = NULL;
int rc;
assert(conn && params);
assert(params->attr);
BT_LE_ASSERT(conn && params);
BT_LE_ASSERT(params->attr);
/* 0-length indications are valid (spec); ble_hs_mbuf_from_flat accepts
* (NULL, 0). But (NULL, len>0) would manifest as a misleading -ENOMEM,
* so catch the programmer error here as a defense-in-depth guard.
*/
assert(params->len == 0 || params->data != NULL);
BT_LE_ASSERT(params->len == 0 || params->data != NULL);
data.attr = params->attr;
data.handle = bt_gatt_attr_get_handle(data.attr);
@@ -635,7 +635,7 @@ static int gatts_nrp_indicate(struct bt_conn *conn, struct bt_gatt_indicate_para
if (bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC) == 0) {
struct bt_gatt_chrc *chrc;
assert(data.attr->user_data);
BT_LE_ASSERT(data.attr->user_data);
chrc = data.attr->user_data;
if ((chrc->properties & BT_GATT_CHRC_INDICATE) == 0) {
@@ -694,7 +694,7 @@ static void gatt_nrp_del(struct gatt_nrp *nrp)
struct gatt_nrp_node *nrp_head;
sys_snode_t *node;
assert(nrp);
BT_LE_ASSERT(nrp);
while (1) {
node = sys_slist_get(&nrp->list);
@@ -735,7 +735,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
}
nrp_node = bt_le_ext_calloc(1, sizeof(*nrp_node));
assert(nrp_node);
BT_LE_ASSERT(nrp_node);
nrp_node->type = type;
@@ -755,7 +755,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
nrp_node->write_req.params = wp;
nrp_node->write_req.data_copy = NULL;
if (wp->length > 0) {
assert(wp->data);
BT_LE_ASSERT(wp->data);
nrp_node->write_req.data_copy = bt_le_ext_malloc(wp->length);
if (nrp_node->write_req.data_copy == NULL) {
LOG_ERR("[N]GattNrpWrDataAllocFail[%u]", wp->length);
@@ -775,7 +775,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
nrp_node->indicate.params_copy = *ip;
nrp_node->indicate.data_copy = NULL;
if (ip->len > 0) {
assert(ip->data);
BT_LE_ASSERT(ip->data);
nrp_node->indicate.data_copy = bt_le_ext_malloc(ip->len);
if (nrp_node->indicate.data_copy == NULL) {
LOG_ERR("[N]GattNrpIndDataAllocFail[%u]", ip->len);
@@ -789,7 +789,7 @@ static int gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params)
break;
}
default:
assert(0);
BT_LE_ASSERT(0);
}
if (sys_slist_is_empty(&nrp->list)) {
@@ -889,31 +889,31 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
/* Fetch and remove the first node from list */
node = sys_slist_get(&nrp->list);
assert(node);
BT_LE_ASSERT(node);
nrp_head = CONTAINER_OF(node, struct gatt_nrp_node, node);
assert(nrp_head->type == type);
BT_LE_ASSERT(nrp_head->type == type);
switch (type) {
case GATTC_NRP_READ_BY_UUID:
assert(nrp_head->read_by_uuid.params == params);
BT_LE_ASSERT(nrp_head->read_by_uuid.params == params);
break;
case GATTC_NRP_READ_LONG:
assert(nrp_head->read_long.params == params);
BT_LE_ASSERT(nrp_head->read_long.params == params);
break;
case GATTC_NRP_READ_SINGLE:
assert(nrp_head->read_single.params == params);
BT_LE_ASSERT(nrp_head->read_single.params == params);
break;
case GATTC_NRP_WRITE_REQ:
assert(nrp_head->write_req.params == params);
BT_LE_ASSERT(nrp_head->write_req.params == params);
free(nrp_head->write_req.data_copy);
nrp_head->write_req.data_copy = NULL;
break;
case GATTC_NRP_SUBSCRIBE:
assert(nrp_head->subscribe.params == params);
BT_LE_ASSERT(nrp_head->subscribe.params == params);
break;
case GATTS_NRP_INDICATE:
assert(nrp_head->indicate.params);
BT_LE_ASSERT(nrp_head->indicate.params);
/* params is the deep copy taken at insert time; cb sees the copy's
* address, not the caller's original (which may have been reused).
@@ -937,7 +937,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
nrp_head->indicate.data_copy = NULL;
break;
default:
assert(0);
BT_LE_ASSERT(0);
}
LOG_DBG("[N]GattNrpFree[%p]", nrp_head);
@@ -958,7 +958,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
if (rc) {
LOG_ERR("[N]GattcNrpRdByUuidFail[%d]", rc);
assert(nrp_head->read_by_uuid.params->func);
BT_LE_ASSERT(nrp_head->read_by_uuid.params->func);
nrp_head->read_by_uuid.params->func(conn, rc, nrp_head->read_by_uuid.params, NULL, 0);
}
} else if (nrp_head->type == GATTC_NRP_READ_LONG) {
@@ -966,7 +966,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
if (rc) {
LOG_ERR("[N]GattcNrpRdLongFail[%d]", rc);
assert(nrp_head->read_long.params->func);
BT_LE_ASSERT(nrp_head->read_long.params->func);
nrp_head->read_long.params->func(conn, rc, nrp_head->read_long.params, NULL, 0);
}
} else if (nrp_head->type == GATTC_NRP_READ_SINGLE) {
@@ -974,7 +974,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
if (rc) {
LOG_ERR("[N]GattcNrpRdSingleFail[%d]", rc);
assert(nrp_head->read_single.params_copy.func);
BT_LE_ASSERT(nrp_head->read_single.params_copy.func);
nrp_head->read_single.params_copy.func(conn, rc,
nrp_head->read_single.params, NULL, 0);
}
@@ -985,7 +985,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
if (rc) {
LOG_ERR("[N]GattcNrpWrFail[%d]", rc);
assert(nrp_head->write_req.params->func);
BT_LE_ASSERT(nrp_head->write_req.params->func);
nrp_head->write_req.params->func(conn, rc, nrp_head->write_req.params);
}
} else if (nrp_head->type == GATTC_NRP_SUBSCRIBE) {
@@ -998,7 +998,7 @@ int bt_le_nimble_gatt_nrp_remove(struct bt_conn *conn, uint8_t type, void *param
if (rc) {
LOG_ERR("[N]GattsNrpIndFail[%d]", rc);
assert(nrp_head->indicate.params->func);
BT_LE_ASSERT(nrp_head->indicate.params->func);
nrp_head->indicate.params->func(conn, nrp_head->indicate.params, rc);
}
}
@@ -8,13 +8,14 @@
#define HOST_NIMBLE_GATT_H_
#include <stdint.h>
#include <assert.h>
#include <zephyr/bluetooth/uuid.h>
#include "host/ble_uuid.h"
#include "host/ble_gatt.h"
#include "utils/assert.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -35,7 +36,7 @@ static inline uint8_t BT_LE_NIMBLE_GATT_UUID_TO_Z(uint8_t type)
} else if (type == BLE_UUID_TYPE_128) {
return BT_UUID_TYPE_128;
} else {
assert(0);
BT_LE_ASSERT(0);
return BT_UUID_TYPE_16;
}
}
@@ -113,7 +114,7 @@ enum {
void bt_le_nimble_gatts_nrp_indicate_cb(uint16_t conn_handle,
int16_t attr_handle,
uint8_t status);
int status);
int bt_le_nimble_gatt_nrp_insert(struct bt_conn *conn, uint8_t type, void *params);
@@ -37,9 +37,9 @@ static int hci_cmd_read_iso_tx_sync(struct net_buf *buf, struct net_buf **rsp)
uint16_t conn_handle;
int status;
assert(rsp);
BT_LE_ASSERT(rsp);
assert(buf->len >= 5);
BT_LE_ASSERT(buf->len >= 5);
conn_handle = sys_get_le16(buf->data + 3);
@@ -87,9 +87,9 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
uint8_t *rp;
int status;
assert(rsp);
BT_LE_ASSERT(rsp);
assert(buf->len >= 18);
BT_LE_ASSERT(buf->len >= 18);
cig_id = buf->data[3];
sdu_interval_c_to_p = sys_get_le24(buf->data + 4);
@@ -101,17 +101,17 @@ static int hci_cmd_set_cig_params(struct net_buf *buf, struct net_buf **rsp)
mtl_p_to_c = sys_get_le16(buf->data + 15);
cis_count = buf->data[17];
assert(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params));
BT_LE_ASSERT(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params));
/* The cis_count can be 0 */
if (cis_count) {
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params));
assert(cis_params);
BT_LE_ASSERT(cis_params);
}
rp_len = sizeof(struct ble_hci_le_set_cig_params_rp) + cis_count * 2;
rp = bt_le_ext_calloc(1, rp_len);
assert(rp);
BT_LE_ASSERT(rp);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params)];
@@ -180,9 +180,9 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
uint8_t *rp;
int status;
assert(rsp);
BT_LE_ASSERT(rsp);
assert(buf->len >= 18);
BT_LE_ASSERT(buf->len >= 18);
cig_id = buf->data[3];
sdu_interval_c_to_p = sys_get_le24(buf->data + 4);
@@ -195,17 +195,17 @@ static int hci_cmd_set_cig_params_test(struct net_buf *buf, struct net_buf **rsp
framing = buf->data[16];
cis_count = buf->data[17];
assert(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params_test));
BT_LE_ASSERT(buf->len >= 18 + cis_count * sizeof(struct ble_hci_le_cis_params_test));
/* The cis_count can be 0 */
if (cis_count) {
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_cis_params_test));
assert(cis_params);
BT_LE_ASSERT(cis_params);
}
rp_len = sizeof(struct ble_hci_le_set_cig_params_test_rp) + cis_count * 2;
rp = bt_le_ext_calloc(1, rp_len);
assert(rp);
BT_LE_ASSERT(rp);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_id = buf->data[18 + i * sizeof(struct ble_hci_le_cis_params_test)];
@@ -269,15 +269,15 @@ static int hci_cmd_create_cis(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 4);
BT_LE_ASSERT(buf->len >= 4);
/* The cis_count shall be at least 1 */
cis_count = buf->data[3];
assert(buf->len >= 4 + cis_count * sizeof(struct ble_hci_le_create_cis_params));
BT_LE_ASSERT(buf->len >= 4 + cis_count * sizeof(struct ble_hci_le_create_cis_params));
cis_params = bt_le_ext_calloc(1, cis_count * sizeof(struct ble_hci_le_create_cis_params));
assert(cis_params);
BT_LE_ASSERT(cis_params);
for (size_t i = 0; i < cis_count; i++) {
cis_params[i].cis_handle = sys_get_le16(buf->data + 4 + i * sizeof(struct ble_hci_le_create_cis_params));
@@ -306,7 +306,7 @@ static int hci_cmd_remove_cig(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 4);
BT_LE_ASSERT(buf->len >= 4);
cig_id = buf->data[3];
@@ -331,7 +331,7 @@ static int hci_cmd_accept_cis_req(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 5);
BT_LE_ASSERT(buf->len >= 5);
cis_handle = sys_get_le16(buf->data + 3);
@@ -357,7 +357,7 @@ static int hci_cmd_reject_cis_req(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 6);
BT_LE_ASSERT(buf->len >= 6);
cis_handle = sys_get_le16(buf->data + 3);
reason = buf->data[5];
@@ -394,7 +394,7 @@ static int hci_cmd_create_big(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 34);
BT_LE_ASSERT(buf->len >= 34);
big_handle = buf->data[3];
adv_handle = buf->data[4];
@@ -456,7 +456,7 @@ static int hci_cmd_create_big_test(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 39);
BT_LE_ASSERT(buf->len >= 39);
big_handle = buf->data[3];
adv_handle = buf->data[4];
@@ -512,7 +512,7 @@ static int hci_cmd_terminate_big(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 5);
BT_LE_ASSERT(buf->len >= 5);
big_handle = buf->data[3];
reason = buf->data[4];
@@ -545,7 +545,7 @@ static int hci_cmd_big_create_sync(struct net_buf *buf, struct net_buf **rsp)
ARG_UNUSED(rsp);
assert(buf->len >= 27);
BT_LE_ASSERT(buf->len >= 27);
big_handle = buf->data[3];
sync_handle = sys_get_le16(buf->data + 4);
@@ -556,7 +556,7 @@ static int hci_cmd_big_create_sync(struct net_buf *buf, struct net_buf **rsp)
num_bis = buf->data[26];
bis = buf->data + 27;
assert(buf->len >= 27 + num_bis);
BT_LE_ASSERT(buf->len >= 27 + num_bis);
ble_hs_lock();
@@ -584,9 +584,9 @@ static int hci_cmd_big_terminate_sync(struct net_buf *buf, struct net_buf **rsp)
uint8_t big_handle;
int status;
assert(rsp);
BT_LE_ASSERT(rsp);
assert(buf->len >= 4);
BT_LE_ASSERT(buf->len >= 4);
big_handle = buf->data[3];
@@ -625,9 +625,9 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp
uint8_t *codec_cfg;
int status;
assert(rsp);
BT_LE_ASSERT(rsp);
assert(buf->len >= 16);
BT_LE_ASSERT(buf->len >= 16);
conn_handle = sys_get_le16(buf->data + 3);
data_path_direction = buf->data[5];
@@ -639,7 +639,7 @@ static int hci_cmd_setup_iso_data_path(struct net_buf *buf, struct net_buf **rsp
codec_cfg_len = buf->data[15];
codec_cfg = buf->data + 16;
assert(buf->len >= 16 + codec_cfg_len);
BT_LE_ASSERT(buf->len >= 16 + codec_cfg_len);
ble_hs_lock();
@@ -677,9 +677,9 @@ static int hci_cmd_remove_iso_data_path(struct net_buf *buf, struct net_buf **rs
uint16_t conn_handle;
int status;
assert(rsp);
BT_LE_ASSERT(rsp);
assert(buf->len >= 6);
BT_LE_ASSERT(buf->len >= 6);
conn_handle = sys_get_le16(buf->data + 3);
data_path_direction = buf->data[5];
@@ -793,7 +793,7 @@ static void iso_evt_rx(uint8_t event, const void *data,
qdata_len = len + 2;
qdata = bt_le_ext_calloc(1, qdata_len);
assert(qdata);
BT_LE_ASSERT(qdata);
qdata[0] = le_meta;
qdata[1] = event;
@@ -1,336 +0,0 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#include <../host/conn_internal.h>
#include "host/ble_hs.h"
#include "host/ble_l2cap.h"
#include "host/ble_hs_mbuf.h"
#include "../../../nimble/host/src/ble_l2cap_priv.h"
#include "common/host.h"
#include "nimble/hs_error.h"
LOG_MODULE_REGISTER(ISO_N2CAP, CONFIG_BT_ISO_LOG_LEVEL);
_Static_assert(CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM && "At least one L2CAP coc shall be supported");
#define OTS_L2CAP_BUF_COUNT (3 * CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM)
#define OTS_L2CAP_MEM_SIZE OS_MEMPOOL_SIZE(OTS_L2CAP_BUF_COUNT, L2CAP_LE_OTS_MTU * 2)
static BT_ISO_EXT_RAM_BSS_ATTR os_membuf_t ots_mem[OTS_L2CAP_MEM_SIZE];
static BT_ISO_EXT_RAM_BSS_ATTR struct os_mempool ots_mbuf_mempool;
static BT_ISO_EXT_RAM_BSS_ATTR struct os_mbuf_pool ots_mbuf_pool;
static BT_ISO_EXT_RAM_BSS_ATTR struct ble_l2cap_chan *ots_chan;
static int ots_l2cap_recv_ready(struct ble_l2cap_chan *chan)
{
struct os_mbuf *sdu_rx;
int rc;
LOG_DBG("[N]L2capOtsRecvReady");
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_rx == NULL) {
LOG_ERR("[N]L2capNoBufForL2capRecv");
return -ENOMEM;
}
rc = ble_l2cap_recv_ready(chan, sdu_rx);
if (rc) {
LOG_ERR("[N]L2capRecvFail[%d]", rc);
os_mbuf_free_chain(sdu_rx);
return rc;
}
return 0;
}
static int ots_l2cap_event_cb(struct ble_l2cap_event *event, void *arg)
{
struct ble_l2cap_chan_info chan_info;
uint16_t result = 0;
size_t sdu_len;
uint8_t *sdu;
int err;
LOG_DBG("[N]L2capOtsEvtCb[%u]", event->type);
switch (event->type) {
case BLE_L2CAP_EVENT_COC_CONNECTED:
if (event->connect.status) {
LOG_ERR("[N]L2capCocConnectFail[%d]", event->connect.status);
return 0;
}
if (ots_chan) {
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]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_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);
bt_le_l2cap_connected(event->connect.conn_handle, chan_info.psm,
chan_info.dcid, chan_info.peer_coc_mtu,
chan_info.scid, chan_info.our_coc_mtu);
return 0;
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
if (ots_chan != event->disconnect.chan) {
LOG_ERR("[N]L2capDisconnectInvCocChan");
return 0;
}
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);
ots_chan = NULL;
return 0;
case BLE_L2CAP_EVENT_COC_ACCEPT:
/* 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]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,
event->accept.chan->coc_tx.credits);
err = bt_le_l2cap_accept(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.chan->coc_tx.credits,
&result);
if (err) {
return err;
}
ARG_UNUSED(result);
return ots_l2cap_recv_ready(event->accept.chan);
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
if (ots_chan != event->receive.chan) {
LOG_ERR("[N]L2capRecvOnInvCocChan");
return 0;
}
assert(event->receive.sdu_rx);
LOG_DBG("[N]L2capCocRecv[%u][%04x][%u]",
event->receive.conn_handle, event->receive.chan->psm,
event->receive.sdu_rx->om_len);
sdu_len = OS_MBUF_PKTLEN(event->receive.sdu_rx);
sdu = bt_le_ext_calloc(1, sdu_len);
assert(sdu);
err = os_mbuf_copydata(event->receive.sdu_rx, 0, sdu_len, sdu);
assert(err == 0);
bt_le_l2cap_received(event->receive.conn_handle,
event->receive.chan->psm,
sdu, sdu_len);
os_mbuf_free_chain(event->receive.sdu_rx);
free(sdu);
return ots_l2cap_recv_ready(event->receive.chan);
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED:
if (ots_chan != event->tx_unstalled.chan) {
LOG_ERR("[N]L2capTxUnstalledOnInvCocChan");
return 0;
}
LOG_WRN("[N]L2capCocTxUnstalled[%u][%d]",
event->tx_unstalled.conn_handle, event->tx_unstalled.status);
/* TODO: transmit the remaining data */
return 0;
default:
return 0;
}
}
int bt_le_nimble_l2cap_chan_connect(uint16_t conn_handle)
{
struct os_mbuf *sdu_rx;
int rc;
if (ots_chan) {
LOG_WRN("[N]L2capOtsChanExist");
return -EALREADY;
}
sdu_rx = os_mbuf_get_pkthdr(&ots_mbuf_pool, 0);
if (sdu_rx == NULL) {
LOG_ERR("[N]L2capNoBufForConnect");
return -ENOMEM;
}
rc = ble_l2cap_connect(conn_handle, L2CAP_LE_OTS_PSM, L2CAP_LE_OTS_MTU,
sdu_rx, ots_l2cap_event_cb, NULL);
if (rc) {
LOG_ERR("[N]L2capConnectFail[%d]", rc);
os_mbuf_free_chain(sdu_rx);
return nimble_err_to_errno(rc);
}
return 0;
}
int bt_le_nimble_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
{
int rc;
if (ots_chan == NULL) {
LOG_WRN("[N]L2capNoOtsChan");
return -ENOTCONN;
}
if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) {
LOG_ERR("[N]L2capUnexpOtsChan[%u][%u]",
ble_l2cap_get_conn_handle(ots_chan), chan->conn->handle);
return -EINVAL;
}
rc = ble_l2cap_disconnect(ots_chan);
if (rc) {
LOG_ERR("[N]L2capDisconnectFail[%d]", rc);
return nimble_err_to_errno(rc);
}
return 0;
}
int bt_le_nimble_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct os_mbuf *sdu_tx;
int rc;
if (ots_chan == NULL) {
LOG_WRN("[N]L2capNoOtsChan");
return -ENOTCONN;
}
if (ble_l2cap_get_conn_handle(ots_chan) != chan->conn->handle) {
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]L2capNoBufForSend");
return -ENOMEM;
}
rc = os_mbuf_append(sdu_tx, buf->data, buf->len);
if (rc) {
LOG_ERR("[N]L2capAppendBufFail[%d]", rc);
os_mbuf_free_chain(sdu_tx);
return -EIO;
}
rc = ble_l2cap_send(ots_chan, sdu_tx);
if (rc) {
if (rc == BLE_HS_ESTALLED) {
/* sdu is queued in tx->sdus[0]; NimBLE will continue on
* BLE_L2CAP_EVENT_COC_TX_UNSTALLED. Do NOT free here.
*/
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]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;
}
int bt_le_nimble_l2cap_init(void)
{
int rc;
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]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]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]L2capCreateL2capSrvFail[%d]", rc);
return rc;
}
#endif /* CONFIG_BT_OTS */
return 0;
}
void bt_le_nimble_l2cap_deinit(void)
{
LOG_DBG("[N]L2capDeinit");
/* TODO: free the ots_mbuf_pool and ots_mbuf_mempool */
#if CONFIG_BT_OTS
/* TODO: destroy the server */
#endif /* CONFIG_BT_OTS */
}
+60 -12
View File
@@ -175,7 +175,7 @@ static void handle_pa_sync_event_safe(struct bt_le_gap_app_param *param)
}
err = bt_le_per_adv_sync_establish_listener(event.pa_sync.sync_handle);
assert(err == 0);
BT_LE_ASSERT(err == 0);
end:
bt_le_host_unlock();
@@ -203,6 +203,29 @@ static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param)
bt_le_host_lock();
#if CONFIG_BT_BLUEDROID_ENABLED
/* See handle_security_change_event_safe: the producer runs on BTU and cannot
* read gatt_conns[], so it sends src_addr and the handle is resolved here.
* HCI identifies the PAST sender by handle and gives no address for it, so
* btu_hcif recovers the BDA from the LCB without its type - match on value. */
{
struct gatt_conn *gatt_conn;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(0,
param->pa_sync_past.src_addr.val,
true);
if (gatt_conn == NULL) {
/* ACL disconnected between PAST enqueue and host processing (scan.c
* names this race). Keep the sync connection-less, like the non-PAST
* path and NimBLE — synced cb fires with conn=NULL. */
LOG_WRN("GapPastUnknownSrc");
event.pa_sync_past.conn_handle = BT_CONN_HANDLE_INVALID;
} else {
event.pa_sync_past.conn_handle = gatt_conn->conn_handle;
}
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
if (event.pa_sync_past.status) {
goto end;
}
@@ -220,7 +243,7 @@ static void handle_pa_sync_past_event_safe(struct bt_le_gap_app_param *param)
}
err = bt_le_per_adv_sync_establish_listener(event.pa_sync_past.sync_handle);
assert(err == 0);
BT_LE_ASSERT(err == 0);
end:
bt_le_host_unlock();
@@ -246,7 +269,7 @@ static void handle_pa_sync_lost_event_safe(struct bt_le_gap_app_param *param)
}
err = bt_le_per_adv_sync_delete(event.pa_sync_lost.sync_handle);
assert(err == 0);
BT_LE_ASSERT(err == 0);
end:
bt_le_host_unlock();
@@ -321,7 +344,7 @@ static void handle_acl_connect_event_safe(struct bt_le_gap_app_param *param)
}
err = bt_le_acl_conn_connected_listener(event.acl_connect.conn_handle);
assert(err == 0);
BT_LE_ASSERT(err == 0);
end:
bt_le_host_unlock();
@@ -348,7 +371,7 @@ static void handle_acl_disconnect_event_safe(struct bt_le_gap_app_param *param)
}
err = bt_le_acl_conn_delete(event.acl_disconnect.conn_handle);
assert(err == 0);
BT_LE_ASSERT(err == 0);
end:
bt_le_host_unlock();
@@ -378,6 +401,27 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
bt_le_host_lock();
#if CONFIG_BT_BLUEDROID_ENABLED
/* Bluedroid's AUTH_CMPL arrives on BTC, which carries no conn_handle and may
* not read gatt_conns[] (iso_task mutates it under this lock). So the producer
* sends dst only and the identity is resolved here instead. NimBLE gets both
* from its own event and fills them at post time. */
{
struct gatt_conn *gatt_conn;
gatt_conn = bt_le_bluedroid_find_gatt_conn_with_addr(event.security_change.dst.type,
event.security_change.dst.val,
false);
if (gatt_conn == NULL) {
LOG_ERR("GapSecChgUnknownDev");
goto end;
}
event.security_change.conn_handle = gatt_conn->conn_handle;
event.security_change.role = gatt_conn->role;
}
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
if (event.security_change.status) {
goto end;
}
@@ -393,11 +437,16 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
if (err) {
goto end;
}
/* Created already encrypted — mirror update()'s *encrypted out-param. */
if (event.security_change.sec_level > BT_SECURITY_L1) {
encrypted = true;
}
} else {
err = bt_le_acl_conn_update(event.security_change.conn_handle,
event.security_change.sec_level,
&encrypted);
assert(err == 0);
BT_LE_ASSERT(err == 0);
}
/* Point conn->le.keys at the bonded LTK the adapter captured, so the lib's
@@ -413,12 +462,11 @@ static void handle_security_change_event_safe(struct bt_le_gap_app_param *param)
err = bt_le_acl_conn_security_changed_listener(event.security_change.conn_handle,
event.security_change.sec_level);
assert(err == 0);
BT_LE_ASSERT(err == 0);
if (encrypted) {
/* TODO: check if bonded or not */
err = bt_le_acl_conn_pairing_completed_listener(event.security_change.conn_handle, true);
assert(err == 0);
(void)bt_le_acl_conn_pairing_completed_listener(event.security_change.conn_handle, true);
}
end:
@@ -483,7 +531,7 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
{
struct bt_le_gap_app_param *param;
assert(data && data_len);
BT_LE_ASSERT(data && data_len);
param = (struct bt_le_gap_app_param *)data;
@@ -519,14 +567,14 @@ void bt_le_gap_handle_event(uint8_t *data, size_t data_len)
handle_bond_delete_event_safe(param);
break;
default:
assert(0);
BT_LE_ASSERT(0);
break;
}
free(data);
}
void bt_le_gap_app_post_event(uint8_t type, void *param)
void bt_le_gap_app_post_event(uint16_t type, void *param)
{
#if CONFIG_BT_BLUEDROID_ENABLED
/* For Bluedroid, post the typed event to the ISO task instead. */
+91 -60
View File
@@ -12,6 +12,7 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/common/bt_str.h>
#include <../host/keys.h>
@@ -53,9 +54,10 @@ bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
ARG_UNUSED(id);
assert(conn && peer);
BT_LE_ASSERT(conn && peer);
/* TODO(privacy): also match init/resp or resolved identity; dst may stay RPA. */
/* Check against conn dst address as it may be the identity address */
if (bt_addr_le_eq(peer, &conn->le.dst)) {
return true;
}
@@ -63,24 +65,12 @@ bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, uint8_t id,
return false;
}
_LIB_IDF
struct bt_conn *bt_conn_ref(struct bt_conn *conn)
{
return conn;
}
_LIB_IDF
void bt_conn_unref(struct bt_conn *conn)
{
ARG_UNUSED(conn);
}
_LIB_ONLY
void bt_conn_foreach(enum bt_conn_type type,
void (*func)(struct bt_conn *conn, void *data),
void *data)
{
assert(type == BT_CONN_TYPE_LE);
BT_LE_ASSERT(type == BT_CONN_TYPE_LE);
for (size_t i = 0; i < ARRAY_SIZE(acl_conns); i++) {
struct bt_conn *conn = &acl_conns[i];
@@ -95,7 +85,7 @@ void bt_conn_foreach(enum bt_conn_type type,
_LIB_ONLY
const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn)
{
assert(conn);
BT_LE_ASSERT(conn);
return &conn->le.dst;
}
@@ -105,19 +95,19 @@ uint8_t bt_conn_index(const struct bt_conn *conn)
{
ptrdiff_t index = 0;
assert(conn);
BT_LE_ASSERT(conn);
switch (conn->type) {
case BT_CONN_TYPE_LE:
index = conn - acl_conns;
assert(index >= 0 && index < ARRAY_SIZE(acl_conns));
BT_LE_ASSERT(index >= 0 && index < ARRAY_SIZE(acl_conns));
break;
case BT_CONN_TYPE_ISO:
index = conn - iso_conns;
assert(index >= 0 && index < ARRAY_SIZE(iso_conns));
BT_LE_ASSERT(index >= 0 && index < ARRAY_SIZE(iso_conns));
break;
default:
assert(0);
BT_LE_ASSERT(0);
break;
}
@@ -126,19 +116,6 @@ uint8_t bt_conn_index(const struct bt_conn *conn)
return (uint8_t)index;
}
#if CONFIG_BT_SMP
_IDF_ONLY
int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec)
{
/* This function is used by CIS to set security level,
* currently always return failure here to make sure
* that before creating CIS, the ACL connection is
* encrypted.
*/
return -ENOTSUP;
}
#endif /* CONFIG_BT_SMP */
static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
{
switch (state) {
@@ -152,7 +129,7 @@ static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
case BT_CONN_DISCONNECTING:
return BT_CONN_STATE_DISCONNECTING;
default:
assert(0);
BT_LE_ASSERT(0);
return 0;
}
}
@@ -160,7 +137,10 @@ static enum bt_conn_state conn_internal_to_public_state(bt_conn_state_t state)
_LIB_ONLY
int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info)
{
assert(conn && info);
BT_LE_ASSERT(conn && info);
/* Callers today only pass ACL (BT_CONN_TYPE_LE). ISO would need
* info->le.* from conn->iso.acl (union); not implemented until needed. */
info->type = conn->type;
info->role = conn->role;
@@ -218,7 +198,6 @@ int bt_conn_cb_register_safe(struct bt_conn_cb *cb)
return err;
}
_NOT_USED
int bt_conn_cb_unregister(struct bt_conn_cb *cb)
{
LOG_DBG("ConnCbUnreg");
@@ -236,6 +215,16 @@ int bt_conn_cb_unregister(struct bt_conn_cb *cb)
return 0;
}
_IDF_ONLY
int bt_conn_cb_unregister_safe(struct bt_conn_cb *cb)
{
int err;
bt_le_host_lock();
err = bt_conn_cb_unregister(cb);
bt_le_host_unlock();
return err;
}
_LIB_ONLY
int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb)
{
@@ -277,7 +266,7 @@ int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb)
_IDF_ONLY
struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size)
{
assert(conns);
BT_LE_ASSERT(conns);
for (size_t i = 0; i < size; i++) {
struct bt_conn *conn = &conns[i];
@@ -322,7 +311,7 @@ struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type)
{
struct bt_conn *conn = NULL;
assert(type == BT_CONN_TYPE_LE || type == BT_CONN_TYPE_ISO);
BT_LE_ASSERT(type == BT_CONN_TYPE_LE || type == BT_CONN_TYPE_ISO);
/* LOG_DBG("ConnLookupHdl[%u][%u]", handle, type); */
@@ -337,11 +326,20 @@ struct bt_conn *bt_conn_lookup_handle(uint16_t handle, enum bt_conn_type type)
struct bt_conn *bt_conn_lookup_index(uint8_t index)
{
struct bt_conn *conn;
if (index >= ARRAY_SIZE(acl_conns)) {
return NULL;
}
return bt_conn_ref(&acl_conns[index]);
conn = &acl_conns[index];
/* Free/wiped slots are type NONE after acl_conn_delete. */
if (conn->type == BT_CONN_TYPE_NONE) {
return NULL;
}
return conn;
}
_IDF_ONLY
@@ -352,17 +350,6 @@ struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle)
return bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE);
}
_IDF_ONLY
struct bt_conn *bt_le_acl_conn_find_safe(uint16_t conn_handle)
{
struct bt_conn *conn = NULL;
/* LOG_DBG("AclConnFind[%u]", conn_handle); */
bt_le_host_lock();
conn = bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE);
bt_le_host_unlock();
return conn;
}
_IDF_ONLY
int bt_le_acl_conn_new(uint16_t conn_handle,
uint8_t role,
@@ -371,7 +358,7 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
{
struct bt_conn *conn;
assert(dst);
BT_LE_ASSERT(dst);
LOG_DBG("AclNew[%u][%u][%u][%s]",
conn_handle, role, sec_level, bt_addr_le_str(dst));
@@ -383,6 +370,10 @@ int bt_le_acl_conn_new(uint16_t conn_handle,
conn->role = role;
conn->state = BT_CONN_CONNECTED;
conn->sec_level = sec_level;
/* AUTH-before-ACL-slot path may create with L2+ directly. */
if (sec_level > BT_SECURITY_L1) {
conn->encrypt = 1;
}
memcpy(&conn->le.dst, dst, sizeof(conn->le.dst));
} else {
LOG_ERR("NoFreeConn[%u]", conn_handle);
@@ -418,6 +409,20 @@ void bt_conn_le_set_ltk(struct bt_conn *conn, const uint8_t *ltk)
LOG_INF("ConnSetLtk[%u][%s]", conn->handle, bt_hex(ltk, 16));
}
#if CONFIG_BT_ISO_UNICAST
/* Defer ACL slot wipe while a CIS still holds iso.acl. */
static bool bt_iso_acl_has_cis(const struct bt_conn *acl)
{
for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) {
if (iso_conns[i].type == BT_CONN_TYPE_ISO && iso_conns[i].iso.acl == acl) {
return true;
}
}
return false;
}
#endif /* CONFIG_BT_ISO_UNICAST */
_IDF_ONLY
int bt_le_acl_conn_delete(uint16_t conn_handle)
{
@@ -432,6 +437,27 @@ int bt_le_acl_conn_delete(uint16_t conn_handle)
return -ENOTCONN;
}
#if CONFIG_BT_ISO_UNICAST
if (bt_iso_acl_has_cis(conn)) {
LOG_INF("AclConnDelDeferred[%u]", conn_handle);
return 0;
}
#endif /* CONFIG_BT_ISO_UNICAST */
/* OTS CoC (and any L2CAP) keep chan->conn on this list. Wipe without
* detaching leaves dangling pointers if ACL slot is reused. */
{
struct bt_l2cap_chan *chan, *tmp;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&conn->channels, chan, tmp, node) {
sys_slist_find_and_remove(&conn->channels, &chan->node);
if (chan->ops != NULL && chan->ops->disconnected != NULL) {
chan->ops->disconnected(chan);
}
chan->conn = NULL;
}
}
/* 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]));
@@ -461,8 +487,8 @@ int bt_le_acl_conn_update(uint16_t conn_handle,
}
if (conn->sec_level < sec_level) {
/* No encryption to encryption */
if (conn->encrypt == 0 && conn->sec_level == BT_SECURITY_L1) {
/* Mark encrypted on first step into L2+ (not only from L1). */
if (conn->encrypt == 0 && sec_level > BT_SECURITY_L1) {
conn->encrypt = 1;
if (encrypted) {
@@ -519,8 +545,8 @@ void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state)
{
bt_conn_state_t old_state;
assert(conn);
assert(conn->type == BT_CONN_TYPE_LE || conn->type == BT_CONN_TYPE_ISO);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(conn->type == BT_CONN_TYPE_LE || conn->type == BT_CONN_TYPE_ISO);
LOG_DBG("ConnSetState[%u][%u]", conn->state, state);
@@ -556,7 +582,7 @@ static int iso_disconnect(struct bt_conn *conn, uint8_t reason)
_IDF_ONLY
int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason)
{
assert(conn);
BT_LE_ASSERT(conn);
LOG_DBG("ConnDisconnect[%u][%u][%02x]", conn->state, conn->type, reason);
@@ -567,10 +593,14 @@ int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason)
}
if (conn->type == BT_CONN_TYPE_ISO) {
return iso_disconnect(conn, reason);
int err = iso_disconnect(conn, reason);
if (err == 0) {
bt_conn_set_state(conn, BT_CONN_DISCONNECTING);
}
return err;
}
assert(0);
BT_LE_ASSERT(0);
case BT_CONN_DISCONNECTING:
return 0;
case BT_CONN_DISCONNECTED:
@@ -627,9 +657,8 @@ int bt_le_acl_conn_disconnected_listener(uint16_t conn_handle, uint8_t reason)
}
}
#if CONFIG_BT_ISO_UNICAST
/* GATT sub/CCC cleanup is ACL-scoped, not ISO-unicast. */
bt_le_acl_conn_disconnected_gatt_listener(conn_handle);
#endif /* CONFIG_BT_ISO_UNICAST */
return 0;
}
@@ -676,6 +705,8 @@ int bt_le_acl_conn_identity_resolved_listener(uint16_t conn_handle,
return -ENOTCONN;
}
/* TODO(privacy): copy identity into conn->le.dst once adapters post resolve. */
SYS_SLIST_FOR_EACH_CONTAINER(&conn_cbs, listener, _node) {
if (listener->identity_resolved) {
listener->identity_resolved(conn, rpa, identity);
+63 -59
View File
@@ -88,7 +88,7 @@ int bt_gatt_service_register(struct bt_gatt_service *svc)
}
uuid = svc->attrs[0].user_data;
assert(uuid);
BT_LE_ASSERT(uuid);
LOG_DBG("GattSvc[%04x]", uuid->val);
@@ -154,7 +154,7 @@ uint16_t bt_gatt_get_mtu(struct bt_conn *conn)
{
uint16_t mtu;
assert(conn);
BT_LE_ASSERT(conn);
#if CONFIG_BT_BLUEDROID_ENABLED
mtu = bt_le_bluedroid_gatt_get_mtu(conn);
@@ -230,7 +230,7 @@ uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr)
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) {
struct bt_gatt_chrc *chrc = attr->user_data;
assert(chrc);
BT_LE_ASSERT(chrc);
handle = chrc->value_handle;
if (handle == 0) {
@@ -267,7 +267,7 @@ struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr)
LOG_DBG("GattAttrNext");
assert(attr);
BT_LE_ASSERT(attr);
handle = bt_gatt_attr_get_handle(attr);
bt_gatt_foreach_attr(handle + 1, handle + 1, find_next, &next);
@@ -359,7 +359,7 @@ static void foreach_attr_type_dyndb(uint16_t start_handle,
for (size_t i = 0; i < svc->attr_count; i++) {
struct bt_gatt_attr *attr = &svc->attrs[i];
assert(attr->uuid);
BT_LE_ASSERT(attr->uuid);
if (gatt_foreach_iter(attr, attr->handle,
start_handle,
@@ -448,7 +448,7 @@ ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t buf_len, uint16_t offset,
const void *value, uint16_t value_len)
{
assert(buf);
BT_LE_ASSERT(buf);
LOG_DBG("GattAttrRd[%u][%u][%u]", buf_len, offset, value_len);
@@ -471,7 +471,7 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
{
int err;
assert(params);
BT_LE_ASSERT(params);
LOG_DBG("GattNtfCb[%u]", params->len);
@@ -486,10 +486,8 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
err = bt_le_nimble_gatts_notify(conn, params);
#endif
/* gatts_notify is synchronous (mbuf-copy + dispatch on return); fire the
* caller's completion cb here so state machines like PACS_FLAG_NOTIFY_RDY
* advance. */
if (err == 0 && params->func != NULL) {
/* Sync notify: complete cb only when conn is known (not broadcast-all). */
if (err == 0 && params->func != NULL && conn != NULL) {
params->func(conn, params->user_data);
}
@@ -499,8 +497,8 @@ int bt_gatt_notify_cb(struct bt_conn *conn, struct bt_gatt_notify_params *params
_LIB_IDF
int bt_gatt_indicate(struct bt_conn *conn, struct bt_gatt_indicate_params *params)
{
assert(params);
assert(params->attr && params->attr->uuid);
BT_LE_ASSERT(params);
BT_LE_ASSERT(params->attr && params->attr->uuid);
LOG_DBG("GattInd[%s]", bt_uuid_str(params->attr->uuid));
@@ -534,7 +532,7 @@ ssize_t bt_gatt_attr_read_service(struct bt_conn *conn,
{
struct bt_uuid *uuid = attr->user_data;
assert(uuid);
BT_LE_ASSERT(uuid);
LOG_DBG("GattAttrRdSvc[%s]", bt_uuid_str(uuid));
@@ -545,7 +543,7 @@ ssize_t bt_gatt_attr_read_service(struct bt_conn *conn,
}
/* BT_UUID_TYPE_32 is not expected for service UUIDs in LE Audio */
assert(uuid->type == BT_UUID_TYPE_128);
BT_LE_ASSERT(uuid->type == BT_UUID_TYPE_128);
return bt_gatt_attr_read(conn, attr, buf, len, offset, BT_UUID_128(uuid)->val, 16);
}
@@ -581,8 +579,8 @@ ssize_t bt_gatt_attr_read_included(struct bt_conn *conn,
uint16_t handle;
incl = attr->user_data;
assert(incl);
assert(incl->user_data);
BT_LE_ASSERT(incl);
BT_LE_ASSERT(incl->user_data);
handle = bt_gatt_attr_get_handle(incl);
uuid = incl->user_data;
@@ -618,8 +616,8 @@ ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn,
struct gatt_chrc pdu;
uint8_t value_len;
assert(chrc);
assert(chrc->uuid);
BT_LE_ASSERT(chrc);
BT_LE_ASSERT(chrc->uuid);
pdu.properties = chrc->properties;
pdu.value_handle = sys_cpu_to_le16(bt_gatt_attr_value_handle(attr));
@@ -661,7 +659,7 @@ static struct bt_gatt_ccc_cfg *gatts_find_free_ccc_cfg(struct bt_gatt_ccc_manage
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);
BT_LE_ASSERT(conn);
for (size_t i = 0; i < ccc->cfg_count; i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
@@ -784,10 +782,10 @@ ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn,
const struct bt_gatt_ccc_cfg *cfg;
uint16_t value;
assert(attr);
BT_LE_ASSERT(attr);
ccc = attr->user_data;
assert(ccc);
BT_LE_ASSERT(ccc);
LOG_DBG("GattAttrRdCcc[%u][%u]", len, offset);
@@ -814,10 +812,10 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
bool new_entry;
uint16_t value;
assert(attr);
BT_LE_ASSERT(attr);
ccc = attr->user_data;
assert(ccc);
BT_LE_ASSERT(ccc);
LOG_DBG("GattAttrWrCcc[%u][%u][%02x]", len, offset, flags);
@@ -910,8 +908,8 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
{
const struct bt_gatt_ccc_managed_user_data *ccc;
assert(conn);
assert(attr && attr->uuid);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(attr && attr->uuid);
LOG_DBG("GattIsSub[%04x][%s]", ccc_type, bt_uuid_str(attr->uuid));
@@ -950,7 +948,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
}
attr = bt_gatt_attr_next(attr);
assert(attr && attr->uuid);
BT_LE_ASSERT(attr && attr->uuid);
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
}
@@ -962,7 +960,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
return false;
}
assert(attr->uuid);
BT_LE_ASSERT(attr->uuid);
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
}
@@ -978,7 +976,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
}
}
assert(attr->uuid);
BT_LE_ASSERT(attr->uuid);
LOG_DBG("GattIsSubAttr[%s][%d]", bt_uuid_str(attr->uuid), __LINE__);
@@ -987,7 +985,7 @@ bool bt_gatt_is_subscribed(struct bt_conn *conn,
}
ccc = attr->user_data;
assert(ccc);
BT_LE_ASSERT(ccc);
/* Check if the connection is subscribed */
for (size_t i = 0; i < ccc->cfg_count; i++) {
@@ -1013,7 +1011,7 @@ static int gatts_ccc_cfg_update(struct bt_conn *conn,
bool value_changed;
ccc = attr->user_data;
assert(ccc);
BT_LE_ASSERT(ccc);
/* LOG_DBG("GattsCccCfgUpd[%04x]", value); */
@@ -1130,10 +1128,10 @@ int bt_gattc_disc_start_safe(uint16_t conn_handle)
_LIB_ONLY
int bt_gatt_discover(struct bt_conn *conn, struct bt_gatt_discover_params *params)
{
assert(conn);
assert(params);
assert(params->start_handle && params->end_handle);
assert(params->start_handle <= params->end_handle);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(params);
BT_LE_ASSERT(params->start_handle && params->end_handle);
BT_LE_ASSERT(params->start_handle <= params->end_handle);
LOG_DBG("GattDisc[%u][%u]", params->start_handle, params->end_handle);
@@ -1276,7 +1274,7 @@ static int gattc_ccc_discover(struct bt_conn *conn, struct bt_gatt_subscribe_par
.val = BT_UUID_GATT_CCC_VAL,
};
assert(params->disc_params);
BT_LE_ASSERT(params->disc_params);
LOG_DBG("GattcCccDisc[%u][%u]", params->value_handle, params->end_handle);
@@ -1310,11 +1308,11 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
bool has_subscription = false;
struct gattc_sub *sub;
assert(conn);
assert(params);
assert(params->notify);
assert(params->value);
assert(params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE ||
BT_LE_ASSERT(conn);
BT_LE_ASSERT(params);
BT_LE_ASSERT(params->notify);
BT_LE_ASSERT(params->value);
BT_LE_ASSERT(params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE ||
(params->end_handle && params->disc_params));
LOG_DBG("GattcSub[%u][%u][%u][%04x]",
@@ -1369,6 +1367,14 @@ int bt_gatt_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *par
params->value_handle = 0; /* unlinked: clear retry guard */
return err;
}
/* Adapter may have returned 0 while leaving ccc unresolved
* (cb got attr==NULL). Do not append a dead subscription. */
if (params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE) {
LOG_ERR("GattcSubCccUnresolved[%u]", params->value_handle);
params->value_handle = 0;
return -ENOENT;
}
} else {
/* Note:
* If the CCC handle is auto-discovered, and during the
@@ -1403,8 +1409,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
bool found = false;
int err = 0;
assert(conn);
assert(params);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(params);
LOG_DBG("GattcUnsub[%04x][%u][%u][%u]",
params->value, params->value_handle, params->ccc_handle, params->end_handle);
@@ -1469,10 +1475,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
LOG_DBG("GattcUnsub[%u][%d]", has_subscription, err);
if (has_subscription || err) {
/* Notify with NULL data to complete unsubscribe */
params->notify(conn, params, NULL, 0);
}
/* Notify with NULL data to complete unsubscribe */
params->notify(conn, params, NULL, 0);
return 0;
}
@@ -1480,8 +1484,8 @@ int bt_gatt_unsubscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *p
_LIB_IDF
int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
{
assert(conn);
assert(params);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(params);
LOG_DBG("GattRd[%u]", params->handle_count);
@@ -1496,7 +1500,7 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
}
if (params->handle_count == 0) {
assert(params->by_uuid.uuid);
BT_LE_ASSERT(params->by_uuid.uuid);
if (params->by_uuid.uuid->type != BT_UUID_TYPE_16) {
LOG_ERR("GattRdInvUuid[%s]", bt_uuid_str(params->by_uuid.uuid));
@@ -1514,11 +1518,11 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
_LIB_IDF
int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
{
assert(conn);
assert(params);
assert(params->func);
assert(params->handle);
assert((!params->data ^ !params->length) == 0);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(params);
BT_LE_ASSERT(params->func);
BT_LE_ASSERT(params->handle);
BT_LE_ASSERT((!params->data ^ !params->length) == 0);
LOG_DBG("GattWr[%u][%u][%u]", params->handle, params->length, params->offset);
@@ -1545,9 +1549,9 @@ int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle,
bool sign, bt_gatt_complete_func_t func,
void *user_data)
{
assert(conn);
assert(handle);
assert((!data ^ !length) == 0);
BT_LE_ASSERT(conn);
BT_LE_ASSERT(handle);
BT_LE_ASSERT((!data ^ !length) == 0);
ARG_UNUSED(func);
ARG_UNUSED(user_data);
@@ -1629,7 +1633,7 @@ void bt_le_acl_conn_bond_deleted_gatt_listener(uint8_t id, const bt_addr_le_t *p
void bt_le_gatt_handle_event(uint8_t *data, size_t data_len)
{
assert(data && data_len);
BT_LE_ASSERT(data && data_len);
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_gatt_handle_event(data, data_len);
+1 -1
View File
@@ -69,7 +69,7 @@ struct net_buf *bt_hci_cmd_create(uint16_t opcode, uint8_t param_len)
LOG_DBG("HciCmdCreate[%04x][%u]", opcode, param_len);
buf = net_buf_alloc(&hci_cmd_pool, K_NO_WAIT);
assert(buf);
BT_LE_ASSERT(buf);
net_buf_reserve(buf, BT_BUF_RESERVE);
+6 -20
View File
@@ -40,7 +40,7 @@ void bt_le_host_lock(void)
if (err) {
/* K_MUTEX_SHORT wait failed: the host stack is wedged. k_mutex_lock has
* already logged self/holder task names. Use libc abort() rather
* than assert(0) — assert is a no-op under NDEBUG, which would
* than BT_LE_ASSERT(0) — assert is a no-op under NDEBUG, which would
* let the caller enter the critical section without the mutex
* held and cause races. abort() halts in every build.
*/
@@ -82,22 +82,15 @@ int bt_le_host_init(void)
goto delete_mutex;
}
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
err = bt_le_l2cap_init();
if (err) {
goto deinit_scan;
}
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
#if CONFIG_BT_BLUEDROID_ENABLED
err = bt_le_bluedroid_gap_init();
if (err) {
goto deinit_l2cap;
goto deinit_scan;
}
err = bt_le_bluedroid_gatt_init();
if (err) {
goto deinit_bluedroid_gatt;
goto deinit_gatt;
}
#else
/* nimble: reset the per-conn GATT cache/NRP arrays (now .bss/PSRAM, no
@@ -108,7 +101,7 @@ int bt_le_host_init(void)
err = bt_le_iso_init();
if (err) {
goto deinit_bluedroid_gatt;
goto deinit_gatt;
}
err = bt_le_iso_task_init();
@@ -120,15 +113,11 @@ int bt_le_host_init(void)
deinit_iso:
bt_le_iso_deinit();
deinit_bluedroid_gatt:
deinit_gatt:
#if CONFIG_BT_BLUEDROID_ENABLED
bt_le_bluedroid_gatt_deinit();
deinit_l2cap:
deinit_scan:
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
deinit_scan: /* only reachable when OTS path is compiled in */
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
bt_le_scan_deinit();
delete_mutex:
k_mutex_delete(&host_mutex);
@@ -148,9 +137,6 @@ void bt_le_host_deinit(void)
bt_le_nimble_gattc_db_deinit();
bt_le_nimble_gatt_nrp_deinit();
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
bt_le_l2cap_deinit();
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
bt_le_scan_deinit();
k_mutex_delete(&host_mutex);
@@ -57,6 +57,9 @@ struct bt_le_gap_app_pa_sync_past_param {
uint8_t adv_phy;
uint16_t per_adv_itvl;
uint8_t adv_ca;
/* PAST sender, not the advertiser in addr above; conn_handle is resolved
* from it on Bluedroid. */
struct bt_le_addr src_addr;
/* ACL conn that delivered the PAST. */
uint16_t conn_handle;
};
@@ -300,7 +303,7 @@ void bt_le_gap_app_biginfo_event(uint8_t *param);
void bt_le_gap_handle_event(uint8_t *data, size_t data_len);
void bt_le_gap_app_post_event(uint8_t type, void *param);
void bt_le_gap_app_post_event(uint16_t type, void *param);
#ifdef __cplusplus
}
@@ -32,8 +32,6 @@ void bt_conn_get_acl_conns(struct bt_conn **conns, uint8_t *count);
struct bt_conn *bt_le_acl_conn_find(uint16_t conn_handle);
struct bt_conn *bt_le_acl_conn_find_safe(uint16_t conn_handle);
int bt_le_acl_conn_new(uint16_t conn_handle,
uint8_t role,
bt_addr_le_t *dst,
@@ -77,7 +77,10 @@ struct bt_le_gatts_notify_tx_event {
uint16_t conn_handle;
uint16_t conn_id;
uint16_t attr_handle;
uint8_t status;
/* int, not uint8_t: NimBLE reports BLE_HS_ERR_ATT_BASE(0x100)+att_code for a
* failed indication; uint8_t truncates 0x10e -> 0x0e == BLE_HS_EDONE (success),
* masking the failure as confirmation. */
int status;
};
/* Bluedroid-side adapter events. NimBLE produces ACL connect/disconnect via
@@ -14,13 +14,13 @@
#include <zephyr/bluetooth/iso.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "utils/assert.h"
#include "utils/iso_attr.h"
#include "utils/mem.h"
#include "common/adv.h"
#include "common/conn.h"
#include "common/iso.h"
#include "common/l2cap.h"
#include "common/scan.h"
#include "common/gatt.h"
#include "common/task.h"
@@ -28,6 +28,7 @@ struct net_buf;
struct bt_le_iso_cb {
void (*cis_dis)(struct net_buf *buf);
void (*cis_est)(struct net_buf *buf);
void (*cis_est_v2)(struct net_buf *buf);
void (*cis_req)(struct net_buf *buf);
void (*big_create)(struct net_buf *buf);
void (*big_term)(struct net_buf *buf);
@@ -1,64 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef HOST_COMMON_L2CAP_H_
#define HOST_COMMON_L2CAP_H_
#include <stdint.h>
#include "sdkconfig.h"
#if CONFIG_BT_BLUEDROID_ENABLED
/* TODO */
#else
#include "nimble/l2cap.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define L2CAP_LE_SUCCESS 0x0000
#define L2CAP_LE_ERR_PSM_NOT_SUPP 0x0002
#define L2CAP_LE_ERR_NO_RESOURCES 0x0004
#define L2CAP_LE_ERR_AUTHENTICATION 0x0005
#define L2CAP_LE_ERR_AUTHORIZATION 0x0006
#define L2CAP_LE_ERR_KEY_SIZE 0x0007
#define L2CAP_LE_ERR_ENCRYPTION 0x0008
#define L2CAP_LE_ERR_INVALID_SCID 0x0009
#define L2CAP_LE_ERR_SCID_IN_USE 0x000A
#define L2CAP_LE_ERR_UNACCEPT_PARAMS 0x000B
#define L2CAP_LE_ERR_INVALID_PARAMS 0x000C
#define L2CAP_LE_OTS_PSM 0x0025
#define L2CAP_LE_OTS_MTU MIN(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU, \
CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU)
int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm,
uint16_t scid, uint16_t mtu,
uint16_t mps, uint16_t credits,
uint16_t *result);
void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm,
uint16_t tx_cid, uint16_t tx_mtu,
uint16_t rx_cid, uint16_t rx_mtu);
void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm);
void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm,
uint8_t *data, uint16_t len);
int bt_le_l2cap_init(void);
void bt_le_l2cap_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* HOST_COMMON_L2CAP_H_ */
@@ -35,6 +35,8 @@ void bt_le_scan_recv_listener(uint16_t event_type,
uint8_t data_len,
uint8_t *data);
struct bt_le_per_adv_sync *bt_le_per_adv_sync_find(uint16_t sync_handle);
struct bt_le_per_adv_sync *bt_le_per_adv_sync_find_safe(uint16_t sync_handle);
int bt_le_per_adv_sync_new(uint16_t sync_handle,
@@ -60,6 +60,7 @@ enum iso_queue_item_type {
ISO_QUEUE_ITEM_TYPE_TIMER_EVENT,
ISO_QUEUE_ITEM_TYPE_GAP_EVENT,
ISO_QUEUE_ITEM_TYPE_GATT_EVENT,
ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT,
ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT,
/* floodable: high-volume best-effort reports, posted non-blocking, newest
+43 -10
View File
@@ -60,6 +60,7 @@ static struct bt_le_iso_cb iso_cb = {
#if CONFIG_BT_ISO_UNICAST
.cis_dis = hci_le_cis_disconnected,
.cis_est = hci_le_cis_established,
.cis_est_v2 = hci_le_cis_established_v2,
#if CONFIG_BT_ISO_PERIPHERAL
.cis_req = hci_le_cis_req,
#endif /* CONFIG_BT_ISO_PERIPHERAL */
@@ -135,12 +136,11 @@ static void hci_le_cis_disconnected(struct net_buf *buf)
LOG_DBG("CisDisconnectedEvt[%u]", evt->handle);
iso = bt_conn_lookup_handle(evt->handle, BT_CONN_TYPE_ISO);
assert(iso);
BT_LE_ASSERT(iso);
iso->err = evt->reason;
bt_iso_disconnected(iso);
bt_conn_unref(iso);
}
static int iso_cis_disconn_evt_listener_safe(uint8_t *data)
@@ -189,6 +189,27 @@ static int iso_cis_est_evt_listener_safe(uint8_t *data)
return 0;
}
static int iso_cis_est_evt_v2_listener_safe(uint8_t *data)
{
struct bt_le_iso_cb *listener = NULL;
struct net_buf buf = {0};
LOG_DBG("CisEstEvtV2Listener");
bt_le_host_lock();
SYS_SLIST_FOR_EACH_CONTAINER(&iso_cbs, listener, node) {
if (listener->cis_est_v2) {
buf.data = data + 1; /* The first octet is subev_code */
listener->cis_est_v2(&buf);
}
}
bt_le_host_unlock();
return 0;
}
#if CONFIG_BT_ISO_PERIPHERAL
static int iso_cis_req_evt_listener_safe(uint8_t *data)
{
@@ -346,7 +367,7 @@ static void handle_iso_event(uint8_t *data, size_t data_len)
#if CONFIG_BT_ISO_UNICAST
if (le_meta == false) {
assert(event == BT_HCI_EVT_DISCONN_COMPLETE);
BT_LE_ASSERT(event == BT_HCI_EVT_DISCONN_COMPLETE);
iso_cis_disconn_evt_listener_safe(data + 2);
return;
}
@@ -359,6 +380,9 @@ static void handle_iso_event(uint8_t *data, size_t data_len)
case BT_HCI_EVT_LE_CIS_ESTABLISHED:
iso_cis_est_evt_listener_safe(data + 2);
break;
case BT_HCI_EVT_LE_CIS_ESTABLISHED_V2:
iso_cis_est_evt_v2_listener_safe(data + 2);
break;
#if CONFIG_BT_ISO_PERIPHERAL
case BT_HCI_EVT_LE_CIS_REQ:
iso_cis_req_evt_listener_safe(data + 2);
@@ -385,14 +409,14 @@ static void handle_iso_event(uint8_t *data, size_t data_len)
break;
#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */
default:
assert(0);
LOG_ERR("HandleIsoEvtUnknown[%u]", event);
break;
}
}
void bt_le_iso_handle_hci_event(uint8_t *data, size_t data_len)
{
assert(data && data_len);
BT_LE_ASSERT(data && data_len);
handle_iso_event(data, data_len);
free(data);
}
@@ -442,6 +466,15 @@ static void iso_tx_sdu_clear(uint16_t handle, bool is_big)
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&iso_tx_sdu_list, sdu_node, sdu_tmp, node) {
iso = sdu_node->chan->iso;
/* Peripheral CIS clears chan->iso during disconnect (before this runs);
* the SDU is orphaned — drop it instead of dereferencing NULL. */
if (iso == NULL) {
sys_slist_remove(&iso_tx_sdu_list, prev, &sdu_node->node);
free((void *)sdu_node->sdu);
free(sdu_node);
continue;
}
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)) {
@@ -614,7 +647,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
void *ud;
int err;
assert(data && data_len == sizeof(*evt));
BT_LE_ASSERT(data && data_len == sizeof(*evt));
bt_le_host_lock();
@@ -647,7 +680,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("IsoTxCompFail[%d]", err);
LOG_WRN("IsoTxCompFail[%d]", err);
}
free(sdu_node);
@@ -659,7 +692,7 @@ void bt_le_iso_handle_tx_comp(uint8_t *data, size_t data_len)
}
chan = iso->iso.chan;
assert(chan);
BT_LE_ASSERT(chan);
cb = NULL;
ud = NULL;
@@ -687,7 +720,7 @@ static void iso_tx_comp_cb(uint16_t conn_handle, void *info, size_t size)
* the event to the ISO task for processing.
*/
assert(size == sizeof(struct bt_iso_tx_cb_info));
BT_LE_ASSERT(size == sizeof(struct bt_iso_tx_cb_info));
evt = bt_le_int_calloc(1, sizeof(*evt));
if (evt == NULL) {
@@ -717,7 +750,7 @@ void bt_le_iso_handle_rx_data(uint8_t *data, size_t data_len)
{
struct net_buf buf = {0};
assert(data && data_len);
BT_LE_ASSERT(data && data_len);
bt_le_host_lock();
net_buf_simple_init_with_data(&buf.b, (void *)data, data_len);
@@ -1,533 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015-2016 Intel Corporation
* SPDX-FileCopyrightText: 2023 Nordic Semiconductor
* SPDX-FileContributor: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/l2cap.h>
#include <../host/conn_internal.h>
#include <ots_l2cap_internal.h>
#include "common/host.h"
#include "common/app/gap.h"
LOG_MODULE_REGISTER(ISO_L2CAP, CONFIG_BT_ISO_LOG_LEVEL);
#define L2CAP_LE_MIN_MTU 23
#define L2CAP_ECRED_MIN_MTU 64
#define L2CAP_LE_CID_DYN_START 0x0040
#define L2CAP_LE_CID_DYN_END 0x007F
#define L2CAP_LE_CID_IS_DYN(_cid) (_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END)
#define L2CAP_LE_PSM_FIXED_START 0x0001
#define L2CAP_LE_PSM_FIXED_END 0x007F
#define L2CAP_LE_PSM_DYN_START 0x0080
#define L2CAP_LE_PSM_DYN_END 0x00FF
#define L2CAP_LE_PSM_IS_DYN(_psm) (_psm >= L2CAP_LE_PSM_DYN_START && _psm <= L2CAP_LE_PSM_DYN_END)
static BT_ISO_EXT_RAM_BSS_ATTR sys_slist_t l2cap_servers;
static struct bt_l2cap_chan *l2cap_lookup_tx_cid(struct bt_conn *conn, uint16_t cid)
{
struct bt_l2cap_chan *chan;
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) {
return chan;
}
}
return NULL;
}
__attribute__((unused))
static struct bt_l2cap_chan *l2cap_lookup_rx_cid(struct bt_conn *conn, uint16_t cid)
{
struct bt_l2cap_chan *chan;
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) {
return chan;
}
}
return NULL;
}
static struct bt_l2cap_chan *l2cap_lookup_psm(struct bt_conn *conn, uint16_t psm)
{
struct bt_l2cap_chan *chan;
SYS_SLIST_FOR_EACH_CONTAINER(&conn->channels, chan, node) {
if (BT_L2CAP_LE_CHAN(chan)->psm == psm) {
return chan;
}
}
return NULL;
}
static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
{
LOG_DBG("L2capChanAdd[%04x]", psm);
/* Attach channel to the connection */
if (sys_slist_find(&conn->channels, &chan->node, NULL)) {
LOG_WRN("L2capChanExists[%04x]", psm);
return false;
}
sys_slist_append(&conn->channels, &chan->node);
chan->conn = conn;
/* Set channel PSM */
BT_L2CAP_LE_CHAN(chan)->psm = psm;
return true;
}
static struct bt_l2cap_server *l2cap_server_lookup_psm(uint16_t psm)
{
struct bt_l2cap_server *server = NULL;
SYS_SLIST_FOR_EACH_CONTAINER(&l2cap_servers, server, node) {
if (server->psm == psm) {
break;
}
}
return server;
}
static inline uint16_t err_to_result(int err)
{
switch (err) {
case -ENOMEM:
return L2CAP_LE_ERR_NO_RESOURCES;
case -EACCES:
return L2CAP_LE_ERR_AUTHORIZATION;
case -EPERM:
return L2CAP_LE_ERR_KEY_SIZE;
case -ENOTSUP:
/* This handle the cases where a fixed channel is registered but
* for some reason (e.g. controller not supporting a feature)
* cannot be used.
*/
return L2CAP_LE_ERR_PSM_NOT_SUPP;
default:
return L2CAP_LE_ERR_UNACCEPT_PARAMS;
}
}
_IDF_ONLY
int bt_le_l2cap_accept(uint16_t conn_handle, uint16_t psm,
uint16_t scid, uint16_t mtu,
uint16_t mps, uint16_t credits,
uint16_t *result)
{
struct bt_l2cap_server *server;
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
int err;
ARG_UNUSED(credits);
LOG_DBG("L2capAccept[%u][%04x][%04x][%u][%u]", conn_handle, psm, scid, mtu, mps);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capAcceptNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
*result = L2CAP_LE_ERR_INVALID_PARAMS;
return -ENOTCONN;
}
/* Check if there is a server registered */
server = l2cap_server_lookup_psm(psm);
if (server == NULL) {
LOG_ERR("SrvNotReg[%04x]", psm);
*result = L2CAP_LE_ERR_PSM_NOT_SUPP;
return -ENOTSUP;
}
if (!L2CAP_LE_CID_IS_DYN(scid)) {
LOG_ERR("NotDynScid[%04x]", scid);
*result = L2CAP_LE_ERR_INVALID_SCID;
return -EINVAL;
}
chan = l2cap_lookup_tx_cid(conn, scid);
if (chan) {
LOG_WRN("ScidUsed[%04x]", scid);
*result = L2CAP_LE_ERR_SCID_IN_USE;
return -EALREADY;
}
if (server->accept == NULL) {
LOG_ERR("SrvAcceptNull");
*result = L2CAP_LE_ERR_INVALID_PARAMS;
return -EIO;
}
err = server->accept(conn, server, &chan);
if (err) {
LOG_ERR("SrvAcceptFail[%d]", err);
*result = err_to_result(err);
return -EIO;
}
if (chan == NULL) {
LOG_ERR("SrvAcceptNullChan");
*result = L2CAP_LE_ERR_NO_RESOURCES;
return -ENOMEM;
}
if (l2cap_chan_add(conn, chan, psm) == false) {
*result = L2CAP_LE_ERR_NO_RESOURCES;
return -ENOMEM;
}
*result = L2CAP_LE_SUCCESS;
return 0;
}
_IDF_ONLY
void bt_le_l2cap_connected(uint16_t conn_handle, uint16_t psm,
uint16_t tx_cid, uint16_t tx_mtu,
uint16_t rx_cid, uint16_t rx_mtu)
{
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
LOG_DBG("L2capConnected[%u][%04x][%04x][%u][%04x][%u]",
conn_handle, psm, tx_cid, tx_mtu, rx_cid, rx_mtu);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capConnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("PsmNotFound[%04x]", psm);
return;
}
BT_L2CAP_LE_CHAN(chan)->tx.cid = tx_cid;
BT_L2CAP_LE_CHAN(chan)->tx.mtu = tx_mtu;
BT_L2CAP_LE_CHAN(chan)->rx.cid = rx_cid;
BT_L2CAP_LE_CHAN(chan)->rx.mtu = rx_mtu;
if (chan->ops->connected) {
chan->ops->connected(chan);
}
}
#define L2CAP_UGLY_TX_DONE_WORKAROUND 1
_IDF_ONLY
void bt_le_l2cap_disconnected(uint16_t conn_handle, uint16_t psm)
{
#if L2CAP_UGLY_TX_DONE_WORKAROUND
struct bt_gatt_ots_l2cap *l2cap_ctx;
struct bt_l2cap_le_chan *l2chan;
#endif /* L2CAP_UGLY_TX_DONE_WORKAROUND */
struct bt_l2cap_chan *chan;
struct bt_conn *conn;
LOG_DBG("L2capDisconnected[%u][%04x]", conn_handle, psm);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capDisconnectedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("PsmNotFound[%04x]", psm);
return;
}
#if L2CAP_UGLY_TX_DONE_WORKAROUND
l2chan = CONTAINER_OF(chan, struct bt_l2cap_le_chan, chan);
l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan);
if (chan->ops->sent && l2cap_ctx->tx_done) {
chan->ops->sent(chan);
}
#endif /* L2CAP_UGLY_TX_DONE_WORKAROUND */
if (chan->ops->disconnected) {
chan->ops->disconnected(chan);
}
sys_slist_find_and_remove(&conn->channels, &chan->node);
chan->conn = NULL;
}
_IDF_ONLY
void bt_le_l2cap_received(uint16_t conn_handle, uint16_t psm,
uint8_t *data, uint16_t len)
{
struct bt_l2cap_chan *chan;
struct net_buf buf = {0};
struct bt_conn *conn;
LOG_DBG("L2capReceived[%u][%04x][%u]", conn_handle, psm, len);
conn = bt_le_acl_conn_find(conn_handle);
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_INF("L2capReceivedNotConn[%u][%u]", conn_handle, BT_CONN_STATE_GET(conn));
return;
}
chan = l2cap_lookup_psm(conn, psm);
if (chan == NULL) {
LOG_ERR("PsmNotFound[%04x]", psm);
return;
}
buf.data = data;
buf.len = len;
if (chan->ops->recv) {
chan->ops->recv(chan, &buf);
}
}
_IDF_ONLY
int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, uint16_t psm)
{
#if !CONFIG_BT_BLUEDROID_ENABLED
int err;
#endif
LOG_DBG("L2capChanConnect[%04x]", psm);
if (chan == NULL) {
LOG_ERR("L2capChanNull");
return -EINVAL;
}
if (conn == NULL || conn->state != BT_CONN_CONNECTED) {
LOG_ERR("L2capChanNotConn[%p]", conn);
return -ENOTCONN;
}
if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) {
LOG_ERR("InvPsm[%04x]", psm);
return -EINVAL;
}
#if CONFIG_BT_BLUEDROID_ENABLED
/* L2CAP COC is not yet implemented in the Bluedroid adapter (no
* bt_le_bluedroid_l2cap_chan_connect exists). Return early so callers
* like bt_gatt_ots_l2cap_connect see a clean -ENOTSUP. */
return -ENOTSUP;
#else
err = bt_le_nimble_l2cap_chan_connect(conn->handle);
if (err) {
return err;
}
l2cap_chan_add(conn, chan, psm);
return 0;
#endif
}
_IDF_ONLY
int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan)
{
#if !CONFIG_BT_BLUEDROID_ENABLED
int err;
#endif
LOG_DBG("L2capChanDisconnect");
if (chan == NULL) {
LOG_ERR("L2capChanNull");
return -EINVAL;
}
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
return -ENOTCONN;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return -ENOTSUP;
#else
err = bt_le_nimble_l2cap_chan_disconnect(chan);
if (err) {
/* If the disconnect failed, remove the channel from the connection.
* Otherwise the removal will be handled by the disconnect callback.
*/
sys_slist_find_and_remove(&chan->conn->channels, &chan->node);
chan->conn = NULL;
}
return err;
#endif
}
_IDF_ONLY
int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
if (chan == NULL || buf == NULL) {
LOG_ERR("L2capChanBufNull[%p][%p]", chan, buf);
return -EINVAL;
}
LOG_DBG("L2capChanSend[%u]", buf->len);
if (chan->conn == NULL || chan->conn->state != BT_CONN_CONNECTED) {
LOG_ERR("L2capChanNotConn[%p]", chan->conn);
return -ENOTCONN;
}
if (buf->len > L2CAP_LE_OTS_MTU) {
LOG_ERR("L2capTooLargeBufToSend[%u][%u]", buf->len, L2CAP_LE_OTS_MTU);
return -EMSGSIZE;
}
#if CONFIG_BT_BLUEDROID_ENABLED
return -ENOTSUP;
#else
return bt_le_nimble_l2cap_chan_send(chan, buf);
#endif
}
_IDF_ONLY
int bt_l2cap_server_register(struct bt_l2cap_server *server)
{
LOG_DBG("L2capSrvReg");
if (server == NULL) {
LOG_ERR("SrvNull");
return -EINVAL;
}
if (server->accept == NULL) {
LOG_ERR("SrvAcceptNull");
return -EINVAL;
}
if (server->sec_level > BT_SECURITY_L4) {
LOG_ERR("InvSecLevel[%u]", server->sec_level);
return -EINVAL;
}
if (server->psm) {
if (server->psm < L2CAP_LE_PSM_FIXED_START ||
server->psm > L2CAP_LE_PSM_DYN_END) {
LOG_ERR("InvPsm[%04x]", server->psm);
return -EINVAL;
}
/* Check if given PSM is already in use */
if (l2cap_server_lookup_psm(server->psm)) {
LOG_WRN("PsmReg");
return -EADDRINUSE;
}
LOG_DBG("SrvPsm[%04x]", server->psm);
} else {
uint16_t psm;
for (psm = L2CAP_LE_PSM_DYN_START;
psm <= L2CAP_LE_PSM_DYN_END; psm++) {
if (l2cap_server_lookup_psm(psm) == NULL) {
break;
}
}
if (psm > L2CAP_LE_PSM_DYN_END) {
LOG_ERR("NoFreeDynPsm");
return -EADDRNOTAVAIL;
}
LOG_DBG("PsmNew[%04x]", psm);
server->psm = psm;
}
if (server->sec_level < BT_SECURITY_L1) {
server->sec_level = BT_SECURITY_L1;
}
sys_slist_append(&l2cap_servers, &server->node);
return 0;
}
int bt_le_l2cap_init(void)
{
int err;
LOG_DBG("L2capInit");
#if CONFIG_BT_OTS
extern int bt_gatt_ots_conn_cb_register(void);
err = bt_gatt_ots_conn_cb_register();
if (err) {
LOG_ERR("OtsConnCbRegFail[%d]", err);
return err;
}
extern int bt_gatt_ots_instances_prepare_v2(void);
err = bt_gatt_ots_instances_prepare_v2();
if (err) {
LOG_ERR("PrepOtsInstsFail[%d]", err);
return err;
}
#endif /* CONFIG_BT_OTS */
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
extern int bt_gatt_ots_l2cap_init_v2(void);
err = bt_gatt_ots_l2cap_init_v2();
if (err) {
LOG_ERR("OtsL2capInitFail[%d]", err);
return err;
}
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
#if CONFIG_BT_BLUEDROID_ENABLED
/* No adapter-level L2CAP init exists (COC not implemented). Don't fail
* host init here — the host-agnostic OTS registrations above are still
* valid; any actual COC connect attempt later returns -ENOTSUP in
* bt_l2cap_chan_connect, so OTS features fail gracefully at use time
* instead of preventing the whole host from coming up. */
#else
err = bt_le_nimble_l2cap_init();
if (err) {
return err;
}
#endif
return 0;
}
void bt_le_l2cap_deinit(void)
{
LOG_DBG("L2capDeinit");
/* TODO: L2CAP server deinit */
}
+10 -13
View File
@@ -198,7 +198,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
assert(adv_addr);
BT_LE_ASSERT(adv_addr);
LOG_INF("PaSyncLookupAddr[%s][%u]", bt_addr_le_str(adv_addr), sid);
@@ -218,7 +218,8 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr(const bt_addr_le_t *ad
return per_adv_sync;
}
static struct bt_le_per_adv_sync *per_adv_sync_find(uint16_t handle)
_IDF_ONLY
struct bt_le_per_adv_sync *bt_le_per_adv_sync_find(uint16_t handle)
{
struct bt_le_per_adv_sync *per_adv_sync = NULL;
@@ -239,7 +240,7 @@ struct bt_le_per_adv_sync *bt_le_per_adv_sync_find_safe(uint16_t sync_handle)
struct bt_le_per_adv_sync *per_adv_sync = NULL;
LOG_DBG("PaSyncFind[%u]", sync_handle);
bt_le_host_lock();
per_adv_sync = per_adv_sync_find(sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
bt_le_host_unlock();
return per_adv_sync;
}
@@ -284,7 +285,7 @@ int bt_le_per_adv_sync_new(uint16_t sync_handle,
return -EINVAL;
}
per_adv_sync = per_adv_sync_find(sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync) {
LOG_WRN("PaSyncExist[%u]", sync_handle);
return -EEXIST;
@@ -327,7 +328,7 @@ int bt_le_per_adv_sync_delete(uint16_t sync_handle)
LOG_DBG("PaSyncDelete[%u]", sync_handle);
per_adv_sync = per_adv_sync_find(sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
@@ -347,7 +348,7 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
LOG_DBG("PaSyncEstabListener[%u]", sync_handle);
per_adv_sync = per_adv_sync_find(sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
@@ -374,10 +375,6 @@ int bt_le_per_adv_sync_establish_listener(uint16_t sync_handle)
}
}
if (info.conn) {
bt_conn_unref(info.conn);
}
return 0;
}
@@ -390,7 +387,7 @@ int bt_le_per_adv_sync_lost_listener(uint16_t sync_handle)
LOG_DBG("PaSyncLostListener[%u]", sync_handle);
per_adv_sync = per_adv_sync_find(sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
@@ -426,7 +423,7 @@ int bt_le_per_adv_sync_report_recv_listener(uint16_t sync_handle,
return -EINVAL;
}
per_adv_sync = per_adv_sync_find(sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", sync_handle);
return -ENODEV;
@@ -458,7 +455,7 @@ void hci_le_biginfo_adv_report(struct net_buf *buf)
/* LOG_DBG("BigInfoRecvListener[%u]", evt->sync_handle); */
per_adv_sync = per_adv_sync_find(evt->sync_handle);
per_adv_sync = bt_le_per_adv_sync_find(evt->sync_handle);
if (per_adv_sync == NULL) {
LOG_ERR("PaSyncNotFound[%u]", evt->sync_handle);
return;
+21 -5
View File
@@ -36,7 +36,14 @@ static BT_ISO_CTRL_BSS_ATTR QueueSetHandle_t iso_queue_set;
static BT_ISO_CTRL_BSS_ATTR TaskHandle_t iso_task_handle;
extern void bt_le_timer_handle_event(void *arg);
extern void bt_le_timer_handle_event(void *arg, size_t gen);
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
/* Defined in esp_ble_audio (ots/adapter/l2cap.c) - the only L2CAP consumer is
* OTS, so the shim lives there. Declared instead of included to keep esp_ble_iso
* free of audio headers; both live in the bt component, so the link resolves. */
extern void bt_le_l2cap_handle_event(void *data, size_t data_len);
#endif
#if CONFIG_BT_ISO_DISPATCH_MONITOR
/* Per-type dispatch timing, indexed by iso_queue_item_type. Written only by
@@ -94,7 +101,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
switch (item->type) {
case ISO_QUEUE_ITEM_TYPE_TIMER_EVENT:
bt_le_timer_handle_event(item->data);
bt_le_timer_handle_event(item->data, item->data_len);
break;
case ISO_QUEUE_ITEM_TYPE_GAP_EVENT:
case ISO_QUEUE_ITEM_TYPE_EXT_ADV_REPORT:
@@ -104,6 +111,11 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
case ISO_QUEUE_ITEM_TYPE_GATT_EVENT:
bt_le_gatt_handle_event(item->data, item->data_len);
break;
#if CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT
case ISO_QUEUE_ITEM_TYPE_L2CAP_EVENT:
bt_le_l2cap_handle_event(item->data, item->data_len);
break;
#endif /* CONFIG_BT_OTS || CONFIG_BT_OTS_CLIENT */
case ISO_QUEUE_ITEM_TYPE_ISO_HCI_EVENT:
case ISO_QUEUE_ITEM_TYPE_BIGINFO_ADV_REPORT:
bt_le_iso_handle_hci_event(item->data, item->data_len);
@@ -118,7 +130,7 @@ static void iso_dispatch_item(const struct iso_queue_item *item)
if (item->data) {
free(item->data);
}
assert(0);
BT_LE_ASSERT(0);
break;
}
@@ -188,9 +200,13 @@ int bt_le_iso_task_post(enum iso_queue_item_type type,
wait = 0;
break;
default:
/* Timer / GATT / HCI / GAP lifecycle: reliable, block until space. */
/* Timer / GATT / HCI / GAP lifecycle. A self-post from iso_task must
* not block (it is the sole consumer -> blocking on its own full queue
* deadlocks); external producers get a bounded wait, not portMAX_DELAY,
* so a wedged iso_task can't freeze esp_timer / the host task and stall
* the ISO data path. */
queue = iso_normal_queue;
wait = portMAX_DELAY;
wait = (xTaskGetCurrentTaskHandle() == iso_task_handle) ? 0 : K_QUEUE_SHORT;
break;
}
+69 -33
View File
@@ -86,7 +86,7 @@ static void bt_iso_sent_cb(struct bt_conn *iso, void *user_data, int err)
struct bt_iso_chan *chan = iso->iso.chan;
struct bt_iso_chan_ops *ops;
assert(chan != NULL && "NUllConnForISOSentCb");
BT_LE_ASSERT(chan != NULL && "NUllConnForISOSentCb");
ops = chan->ops;
@@ -135,7 +135,6 @@ void hci_iso(struct net_buf *buf)
iso(buf)->index = bt_conn_index(iso);
bt_iso_recv(iso, buf, flags);
bt_conn_unref(iso);
}
static void iso_get_and_clear_cb(struct bt_conn *conn, struct net_buf *buf, bt_conn_tx_cb_t *cb,
@@ -288,6 +287,14 @@ static int validate_iso_setup_data_path_parms(const struct bt_iso_chan *chan, ui
return -EINVAL;
}
/* LE Setup ISO Data Path is one HCI command (251B usable in the 255B pool):
* sizeof(cp)+cc_len must fit, else uint8 param_len wraps and overflows. */
CHECKIF(path->cc_len > 251 - sizeof(struct bt_hci_cp_le_setup_iso_path)) {
LOG_ERR("InvCcLenTooLarge[%u]", path->cc_len);
return -EINVAL;
}
return 0;
}
@@ -432,14 +439,14 @@ void bt_iso_connected(struct bt_conn *iso)
bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTED);
if (chan->ops->connected) {
if (chan->ops && chan->ops->connected) {
chan->ops->connected(chan);
}
}
static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
{
assert(chan->iso != NULL && "NullConnForIsoChan");
BT_LE_ASSERT(chan->iso != NULL && "NullConnForIsoChan");
const uint8_t conn_type = chan->iso->iso.info.type;
@@ -452,12 +459,12 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
* the callback and to be more similar to the ACL disconnected callback. This also means
* that the channel cannot be reused or memset in the callback
*/
if (chan->ops->disconnected) {
if (chan->ops && chan->ops->disconnected) {
chan->ops->disconnected(chan, reason);
}
/* The peripheral does not have the concept of a CIG, so once a CIS
* disconnects it is completely freed by unref'ing it
* disconnects it is completely freed.
*/
if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) &&
(conn_type == BT_ISO_CHAN_TYPE_CENTRAL || conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL)) {
@@ -466,7 +473,6 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
if (conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL) {
/* Release iso conn slot back to the pool. */
chan->iso->type = BT_CONN_TYPE_NONE;
bt_conn_unref(chan->iso);
chan->iso = NULL;
#if defined(CONFIG_BT_ISO_CENTRAL)
} else {
@@ -476,7 +482,7 @@ static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, uint8_t reason)
/* Update CIG state */
cig = get_cig(chan);
assert(cig != NULL && "CigNull");
BT_LE_ASSERT(cig != NULL && "CigNull");
is_chan_connected = false;
SYS_SLIST_FOR_EACH_CONTAINER(&cig->cis_channels, cis_chan, node) {
@@ -679,7 +685,7 @@ void bt_iso_recv(struct bt_conn *iso, struct net_buf *buf, uint8_t flags)
chan = iso_chan(iso);
if (chan == NULL) {
LOG_ERR("NoChanForIsoRecv");
} else if (chan->ops->recv != NULL) {
} else if (chan->ops && chan->ops->recv) {
chan->ops->recv(chan, &iso_info, buf->data, buf->len);
}
}
@@ -854,12 +860,22 @@ int bt_iso_chan_disconnect(struct bt_iso_chan *chan)
void bt_iso_cleanup_acl(struct bt_conn *iso)
{
LOG_DBG("IsoCleanupAcl[%u]", iso->iso.acl ? iso->iso.acl->handle : UINT16_MAX);
struct bt_conn *acl = iso->iso.acl;
if (iso->iso.acl) {
bt_conn_unref(iso->iso.acl);
iso->iso.acl = NULL;
LOG_INF("IsoCleanupAcl[%u]", acl ? acl->handle : UINT16_MAX);
if (acl == NULL) {
return;
}
iso->iso.acl = NULL;
#if CONFIG_BT_ISO_UNICAST
/* Finalize deferred ACL wipe once no CIS still points at it. */
if (acl->state == BT_CONN_DISCONNECTED) {
(void)bt_le_acl_conn_delete(acl->handle);
}
#endif /* CONFIG_BT_ISO_UNICAST */
}
static void store_cis_info(const struct bt_hci_evt_le_cis_established *evt, struct bt_conn *iso)
@@ -1009,6 +1025,10 @@ static void store_cis_info_v2(const struct bt_hci_evt_le_cis_established_v2 *evt
LOG_DBG("StoreCisInfoV2");
chan = iso_conn->chan;
if (chan == NULL || chan->qos == NULL) {
LOG_ERR("CisChanOrQosNullV2[%p]", chan);
return;
}
rx = chan->qos->rx;
tx = chan->qos->tx;
@@ -1085,8 +1105,6 @@ void hci_le_cis_established(struct net_buf *buf)
iso->err = evt->status;
bt_iso_disconnected(iso);
} /* else we wait for disconnect event */
bt_conn_unref(iso);
}
void hci_le_cis_established_v2(struct net_buf *buf)
@@ -1127,8 +1145,6 @@ void hci_le_cis_established_v2(struct net_buf *buf)
iso->err = evt->status;
bt_iso_disconnected(iso);
} /* else we wait for disconnect event */
bt_conn_unref(iso);
}
#if defined(CONFIG_BT_ISO_PERIPHERAL)
@@ -1286,7 +1302,6 @@ void hci_le_cis_req(struct net_buf *buf)
if (iso) {
LOG_ERR("InvCisHdl[%u]", cis_handle);
hci_le_reject_cis(cis_handle, BT_HCI_ERR_CONN_LIMIT_EXCEEDED);
bt_conn_unref(iso);
return;
}
@@ -1301,8 +1316,6 @@ void hci_le_cis_req(struct net_buf *buf)
/* Add ISO connection */
iso = bt_conn_add_iso(acl);
bt_conn_unref(acl);
if (!iso) {
LOG_ERR("AddCisToAclFail[%u]", acl_handle);
hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
@@ -1318,7 +1331,8 @@ void hci_le_cis_req(struct net_buf *buf)
if (err) {
LOG_INF("AppRejectedCis[%d]", err);
bt_iso_cleanup_acl(iso);
bt_conn_unref(iso);
/* Release the ISO slot back to the pool. */
iso->type = BT_CONN_TYPE_NONE;
hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
return;
}
@@ -1329,8 +1343,10 @@ void hci_le_cis_req(struct net_buf *buf)
err = hci_le_accept_cis(cis_handle);
if (err) {
bt_iso_cleanup_acl(iso);
bt_conn_unref(iso);
/* iso_accept already bound the app's chan: full teardown so it isn't left
CONNECTING with a dangling chan->iso once the slot is released. */
iso->err = BT_HCI_ERR_INSUFFICIENT_RESOURCES;
bt_iso_disconnected(iso);
hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
return;
}
@@ -1347,7 +1363,7 @@ static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl)
return NULL;
}
iso->iso.acl = bt_conn_ref(acl);
iso->iso.acl = acl;
return iso;
}
@@ -1651,7 +1667,7 @@ static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan)
return NULL;
}
assert(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs) && "InvCIGID");
BT_LE_ASSERT(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs) && "InvCIGID");
return &cigs[iso_chan->iso->iso.info.unicast.cig_id];
}
@@ -1726,7 +1742,6 @@ static void cleanup_cig(struct bt_iso_cig *cig)
if (cis->iso != NULL) {
/* Release iso conn slot back to the pool. */
cis->iso->type = BT_CONN_TYPE_NONE;
bt_conn_unref(cis->iso);
cis->iso = NULL;
}
@@ -1761,6 +1776,27 @@ static bool valid_cig_param(const struct bt_iso_cig_param *param, bool advanced,
return false;
}
/* Built in the 255B HCI cmd pool (hci.c): BT_BUF_RESERVE + the 3B cmd header
* eat into it first, and test params cost 14B per CIS instead of 9B, so
* 31 CIS (spec max) never fits. Cap num_cis to the real budget. */
{
const size_t avail = 255U - BT_BUF_RESERVE - BT_HCI_CMD_HDR_SIZE;
size_t hdr_sz = sizeof(struct bt_hci_cp_le_set_cig_params);
size_t cis_sz = sizeof(struct bt_hci_cis_params);
#if defined(CONFIG_BT_ISO_TEST_PARAMS)
if (advanced) {
hdr_sz = sizeof(struct bt_hci_cp_le_set_cig_params_test);
cis_sz = sizeof(struct bt_hci_cis_params_test);
}
#endif /* CONFIG_BT_ISO_TEST_PARAMS */
if (param->num_cis > (avail - hdr_sz) / cis_sz) {
LOG_ERR("TooLargeNumCisForHciCmd[%u][%u]", param->num_cis, advanced);
return false;
}
}
for (uint8_t i = 0; i < param->num_cis; i++) {
struct bt_iso_chan *cis = param->cis_channels[i];
@@ -1991,7 +2027,8 @@ static void restore_cig(struct bt_iso_cig *cig, uint8_t existing_num_cis)
* bt_iso_cig_reconfigure was called
*/
if (cis->iso != NULL && cis->iso->iso.info.unicast.cis_id >= existing_num_cis) {
bt_conn_unref(cis->iso);
/* Release the ISO slot back to the pool. */
cis->iso->type = BT_CONN_TYPE_NONE;
cis->iso = NULL;
sys_slist_remove(&cig->cis_channels, prev, &cis->node);
@@ -2243,12 +2280,12 @@ int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count)
struct bt_iso_chan *iso_chan = param[i].iso_chan;
struct bt_iso_cig *cig;
iso_chan->iso->iso.acl = bt_conn_ref(param[i].acl);
iso_chan->iso->iso.acl = param[i].acl;
bt_conn_set_state(iso_chan->iso, BT_CONN_INITIATING);
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING);
cig = get_cig(iso_chan);
assert(cig && "CigNull");
BT_LE_ASSERT(cig && "CigNull");
cig->state = BT_ISO_CIG_STATE_ACTIVE;
}
@@ -2313,7 +2350,6 @@ static void cleanup_big(struct bt_iso_big *big)
if (bis->iso != NULL) {
/* Release iso conn slot back to the pool. */
bis->iso->type = BT_CONN_TYPE_NONE;
bt_conn_unref(bis->iso);
bis->iso = NULL;
}
@@ -2428,7 +2464,7 @@ static int hci_le_create_big(struct bt_le_ext_adv *padv, struct bt_iso_big *big,
}
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
assert(bis != NULL && "BisNull");
BT_LE_ASSERT(bis != NULL && "BisNull");
/* All BIS will share the same QOS */
qos = bis->qos->tx;
@@ -2491,7 +2527,7 @@ static int hci_le_create_big_test(const struct bt_le_ext_adv *padv, struct bt_is
}
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
assert(bis != NULL && "BisNull");
BT_LE_ASSERT(bis != NULL && "BisNull");
/* All BIS will share the same QOS */
qos = bis->qos;
@@ -2972,7 +3008,7 @@ int bt_iso_big_terminate(struct bt_iso_big *big)
}
bis = SYS_SLIST_PEEK_HEAD_CONTAINER(&big->bis_channels, bis, node);
assert(bis != NULL && "BisNull");
BT_LE_ASSERT(bis != NULL && "BisNull");
if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) &&
bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER) {
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdlib.h>
#include <zephyr/logging/log.h>
#include "esp_log.h"
#include "utils/assert.h"
#define TAG "ISO_ASSERT"
/* Always logged (no LOG_LEVEL gate) — this is the last message before abort, and
* the user needs the context to diagnose. */
void bt_le_assert(const char *tag, size_t info,
const char *file, int line, const char *func)
{
esp_log_write(ESP_LOG_ERROR, TAG,
BT_ISO_LOG_COLOR_E
"E (%lu) %s: Assert[%s][info=%u][%s:%d][%s]"
BT_ISO_LOG_RESET_COLOR "\n",
esp_log_timestamp(), TAG,
tag, (unsigned)info, file, line, func);
abort();
}
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Also registered as lib_ext_funcs._assert, so tag/info stay free-form for the
* lib's own call sites; BT_LE_ASSERT() passes the stringified condition. */
void bt_le_assert(const char *tag, size_t info,
const char *file, int line, const char *func) __attribute__((noreturn));
/* Use instead of assert(): CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE sets
* -DNDEBUG, which turns every assert() into a no-op and lets execution fall
* through onto the very pointer it was guarding. This one always checks. */
#define BT_LE_ASSERT(_cond) \
do { \
if (!(_cond)) { \
bt_le_assert(#_cond, 0, __FILE__, __LINE__, __func__); \
} \
} while (0)
#ifdef __cplusplus
}
#endif
+4 -2
View File
@@ -24,6 +24,8 @@
#else
#include "host/ble_gap.h"
#include "host/ble_store.h"
#include "utils/assert.h"
#endif
LOG_MODULE_REGISTER(ISO_UTILS, CONFIG_BT_ISO_LOG_LEVEL);
@@ -40,7 +42,7 @@ void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info,
int num = esp_ble_get_bond_device_num();
esp_ble_bond_dev_t *list;
assert(func);
BT_LE_ASSERT(func);
(void)id;
LOG_DBG("[B]ForeachBond[%d]", num);
@@ -135,7 +137,7 @@ void bt_foreach_bond(uint8_t id, void (*func)(const struct bt_bond_info *info,
ble_addr_t peers[CONFIG_BT_MAX_PAIRED];
int num = 0;
assert(func);
BT_LE_ASSERT(func);
(void)id;
if (ble_store_util_bonded_peers(peers, &num, ARRAY_SIZE(peers)) != 0) {
+42 -19
View File
@@ -28,11 +28,11 @@ static void iso_timer_cb(void *arg)
struct k_work *work = arg;
int err;
assert(work);
assert(work->timer);
assert(work->handler);
BT_LE_ASSERT(work);
BT_LE_ASSERT(work->timer);
BT_LE_ASSERT(work->handler);
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, 0);
err = bt_le_iso_task_post(ISO_QUEUE_ITEM_TYPE_TIMER_EVENT, work, work->gen);
if (err) {
LOG_ERR("TimerCbPostFail[%d]", err);
}
@@ -40,7 +40,7 @@ static void iso_timer_cb(void *arg)
int k_work_submit(struct k_work *work)
{
assert(work);
BT_LE_ASSERT(work);
if (work->handler == NULL) {
LOG_WRN("TimerSubmitHdlrNull");
@@ -65,7 +65,7 @@ bool k_work_is_pending(struct k_work *work)
{
bool is_pending;
assert(work);
BT_LE_ASSERT(work);
if (work->handler == NULL) {
LOG_WRN("TimerIsPendingHdlrNull");
@@ -81,20 +81,24 @@ bool k_work_is_pending(struct k_work *work)
void k_work_init(struct k_work *work, k_work_handler_t handler)
{
assert(work);
BT_LE_ASSERT(work);
/* Clear timer/timeout_us/gen too: callers must not have to zero-init */
memset(work, 0, sizeof(*work));
work->handler = handler;
}
struct k_work_delayable *k_work_delayable_from_work(struct k_work *work)
{
assert(work);
BT_LE_ASSERT(work);
return (struct k_work_delayable *)work;
}
void k_work_init_delayable(struct k_work_delayable *dwork,
k_work_handler_t handler)
{
assert(dwork);
BT_LE_ASSERT(dwork);
const esp_timer_create_args_t timer_args = {
.callback = &iso_timer_cb,
@@ -123,13 +127,18 @@ void k_work_deinit_delayable(struct k_work_delayable *dwork)
{
int err;
assert(dwork);
BT_LE_ASSERT(dwork);
if (dwork->work.timer == NULL) {
LOG_INF("TimerDeinitNotCreated");
return;
}
dwork->work.gen++;
/* esp_timer_delete rejects a running timer */
esp_timer_stop(dwork->work.timer);
err = esp_timer_delete(dwork->work.timer);
if (err) {
LOG_ERR("TimerDeinitDelFail[%d]", err);
@@ -141,7 +150,7 @@ void k_work_deinit_delayable(struct k_work_delayable *dwork)
int k_work_cancel_delayable(struct k_work_delayable *dwork)
{
assert(dwork);
BT_LE_ASSERT(dwork);
if (dwork->work.timer == NULL) {
LOG_INF("TimerCancelNotCreated");
@@ -149,6 +158,7 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork)
}
esp_timer_stop(dwork->work.timer);
dwork->work.gen++;
return 0;
}
@@ -156,7 +166,7 @@ int k_work_cancel_delayable(struct k_work_delayable *dwork)
bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork,
struct k_work_sync *sync)
{
assert(dwork);
BT_LE_ASSERT(dwork);
ARG_UNUSED(sync);
@@ -167,6 +177,8 @@ bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork,
esp_timer_stop(dwork->work.timer);
dwork->work.gen++;
/* TODO: check the return result */
return false;
}
@@ -175,7 +187,7 @@ int k_work_schedule(struct k_work_delayable *dwork, k_timeout_t ms)
{
int err;
assert(dwork);
BT_LE_ASSERT(dwork);
if (dwork->work.timer == NULL) {
LOG_WRN("TimerSchNotCreated");
@@ -184,6 +196,8 @@ int k_work_schedule(struct k_work_delayable *dwork, k_timeout_t ms)
esp_timer_stop(dwork->work.timer);
dwork->work.gen++;
dwork->work.timeout_us = esp_timer_get_time() + (int64_t)ms * 1000;
if (ms == K_NO_WAIT) {
@@ -204,7 +218,7 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms)
{
int err;
assert(dwork);
BT_LE_ASSERT(dwork);
if (dwork->work.timer == NULL) {
LOG_WRN("TimerReschNotCreated");
@@ -213,6 +227,8 @@ int k_work_reschedule(struct k_work_delayable *dwork, k_timeout_t ms)
esp_timer_stop(dwork->work.timer);
dwork->work.gen++;
dwork->work.timeout_us = esp_timer_get_time() + (int64_t)ms * 1000;
if (ms == K_NO_WAIT) {
@@ -233,8 +249,8 @@ int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_
{
int err;
assert(dwork);
assert(period_us > 0);
BT_LE_ASSERT(dwork);
BT_LE_ASSERT(period_us > 0);
if (dwork->work.timer == NULL) {
LOG_WRN("TimerPeriodicNotCreated");
@@ -243,6 +259,8 @@ int k_work_schedule_periodic_us(struct k_work_delayable *dwork, uint64_t period_
esp_timer_stop(dwork->work.timer);
dwork->work.gen++;
err = esp_timer_start_periodic(dwork->work.timer, period_us);
if (err) {
LOG_ERR("TimerPeriodicStartFail[%d]", err);
@@ -265,7 +283,7 @@ k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork)
k_timeout_t timeout;
int64_t delta_us;
assert(dwork);
BT_LE_ASSERT(dwork);
if (dwork->work.timer == NULL) {
LOG_WRN("TimerRemainingNotCreated");
@@ -286,9 +304,14 @@ k_timeout_t k_work_delayable_remaining_get(struct k_work_delayable *dwork)
return timeout;
}
void bt_le_timer_handle_event(void *arg)
void bt_le_timer_handle_event(void *arg, size_t gen)
{
struct k_work *work = arg;
k_work_submit_safe(work);
bt_le_host_lock();
/* Drop the event if the work was re-armed or cancelled after it fired */
if ((uint32_t)gen == work->gen) {
k_work_submit(work);
}
bt_le_host_unlock();
}
@@ -41,7 +41,6 @@
#endif /* CONFIG_BT_BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER */
#define CONFIG_BT_MAX_CONN CONFIG_BT_ACL_CONNECTIONS
#define CONFIG_BT_SMP CONFIG_BT_BLE_SMP_ENABLE
#define CONFIG_BT_MAX_PAIRED CONFIG_BT_SMP_MAX_BONDS
#if CONFIG_BT_ISO_UNICAST && CONFIG_BT_ISO_BROADCAST
@@ -57,7 +56,6 @@ _Static_assert(CONFIG_BT_ISO_MAX_CHAN == 0, "Too large ISO channels");
#else /* CONFIG_BT_BLUEDROID_ENABLED */
#define CONFIG_BT_MAX_CONN CONFIG_BT_NIMBLE_MAX_CONNECTIONS
#define CONFIG_BT_SMP CONFIG_BT_NIMBLE_SECURITY_ENABLE
#define CONFIG_BT_MAX_PAIRED CONFIG_BT_NIMBLE_MAX_BONDS
#if CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES
@@ -231,44 +231,6 @@ int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info);
*/
int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason);
/** @brief Set security level for a connection.
*
* This function enable security (encryption) for a connection. If the device
* has bond information for the peer with sufficiently strong key encryption
* will be enabled. If the connection is already encrypted with sufficiently
* strong key this function does nothing.
*
* If the device has no bond information for the peer and is not already paired
* then the pairing procedure will be initiated. Note that @p sec has no effect
* on the security level selected for the pairing process. The selection is
* instead controlled by the values of the registered @ref bt_conn_auth_cb. If
* the device has bond information or is already paired and the keys are too
* weak then the pairing procedure will be initiated.
*
* This function may return an error if the required level of security defined using
* @p sec is not possible to achieve due to local or remote device limitation
* (e.g., input output capabilities), or if the maximum number of paired devices
* has been reached.
*
* This function may return an error if the pairing procedure has already been
* initiated by the local device or the peer device.
*
* @note When @kconfig{CONFIG_BT_SMP_SC_ONLY} is enabled then the security
* level will always be level 4.
*
* @note When @kconfig{CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY} is enabled then the
* security level will always be level 3.
*
* @note When @ref BT_SECURITY_FORCE_PAIR within @p sec is enabled then the pairing
* procedure will always be initiated.
*
* @param conn Connection object.
* @param sec Requested minimum security level.
*
* @return 0 on success or negative error
*/
int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec);
enum bt_security_err {
/** Security procedure successful. */
BT_SECURITY_ERR_SUCCESS,
@@ -417,6 +379,7 @@ int bt_conn_cb_register_safe(struct bt_conn_cb *cb);
* @retval -ENOENT if @p cb was not registered
*/
int bt_conn_cb_unregister(struct bt_conn_cb *cb);
int bt_conn_cb_unregister_safe(struct bt_conn_cb *cb);
/**
* @brief Register a callback structure for connection events.
@@ -1294,6 +1294,8 @@ int bt_iso_big_terminate_safe(struct bt_iso_big *big);
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_iso_big_sync(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param,
struct bt_iso_big **out_big);
int bt_iso_big_sync_safe(struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param,
struct bt_iso_big **out_big);
@@ -9,7 +9,6 @@
#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <errno.h>
#include <zephyr/sys/slist.h>
@@ -21,6 +20,8 @@
#include "freertos/semphr.h"
#include "toolchain.h"
#include "utils/assert.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -36,17 +37,17 @@ struct k_mutex {
static inline void k_mutex_create(struct k_mutex *mutex)
{
assert(mutex);
assert(mutex->handle == NULL);
BT_LE_ASSERT(mutex);
BT_LE_ASSERT(mutex->handle == NULL);
mutex->handle = xSemaphoreCreateRecursiveMutex();
assert(mutex->handle);
BT_LE_ASSERT(mutex->handle);
}
static inline void k_mutex_delete(struct k_mutex *mutex)
{
assert(mutex);
assert(mutex->handle);
BT_LE_ASSERT(mutex);
BT_LE_ASSERT(mutex->handle);
vSemaphoreDelete(mutex->handle);
mutex->handle = NULL;
@@ -65,8 +66,8 @@ static inline void k_mutex_delete(struct k_mutex *mutex)
static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout)
{
assert(mutex);
assert(mutex->handle);
BT_LE_ASSERT(mutex);
BT_LE_ASSERT(mutex->handle);
if (xSemaphoreTakeRecursive(mutex->handle, timeout) == pdTRUE) {
return 0;
@@ -89,8 +90,8 @@ static inline int k_mutex_lock(struct k_mutex *mutex, uint32_t timeout)
static inline int k_mutex_unlock(struct k_mutex *mutex)
{
assert(mutex);
assert(mutex->handle);
BT_LE_ASSERT(mutex);
BT_LE_ASSERT(mutex->handle);
if (xSemaphoreGiveRecursive(mutex->handle) != pdTRUE) {
K_MUTEX_LOG_ERR("UnlockFail");
@@ -112,18 +113,18 @@ struct k_sem {
static inline void k_sem_create(struct k_sem *sem)
{
assert(sem);
assert(sem->handle == NULL);
BT_LE_ASSERT(sem);
BT_LE_ASSERT(sem->handle == NULL);
sem->handle = xSemaphoreCreateBinary();
assert(sem->handle);
BT_LE_ASSERT(sem->handle);
sem->result = 0;
}
static inline void k_sem_delete(struct k_sem *sem)
{
assert(sem);
assert(sem->handle);
BT_LE_ASSERT(sem);
BT_LE_ASSERT(sem->handle);
vSemaphoreDelete(sem->handle);
sem->handle = NULL;
@@ -141,8 +142,8 @@ static inline void k_sem_delete(struct k_sem *sem)
static inline int k_sem_take(struct k_sem *sem, uint32_t timeout)
{
assert(sem);
assert(sem->handle);
BT_LE_ASSERT(sem);
BT_LE_ASSERT(sem->handle);
/* Do NOT touch sem->result here. The producer may have already written
* it and called k_sem_give before this take ran (BTU/HCI cb on a
@@ -164,8 +165,8 @@ static inline int k_sem_take(struct k_sem *sem, uint32_t timeout)
static inline int k_sem_give(struct k_sem *sem)
{
assert(sem);
assert(sem->handle);
BT_LE_ASSERT(sem);
BT_LE_ASSERT(sem->handle);
if (xSemaphoreGive(sem->handle) != pdTRUE) {
K_SEM_LOG_ERR("GiveFail");
@@ -181,13 +182,20 @@ static inline int k_sem_give(struct k_sem *sem)
* caller would see uninitialized response data. */
static inline void k_sem_reset(struct k_sem *sem)
{
assert(sem);
assert(sem->handle);
BT_LE_ASSERT(sem);
BT_LE_ASSERT(sem->handle);
xQueueReset(sem->handle);
sem->result = 0;
}
/* Queue */
/* Bounded wait for a reliable-tier task-queue post instead of portMAX_DELAY,
* so a wedged consumer can't freeze an external producer (esp_timer / host
* task) and stall the ISO data path. */
#define K_QUEUE_SHORT (1000 / portTICK_PERIOD_MS)
/* Timer */
typedef uint32_t k_timeout_t;
@@ -202,6 +210,10 @@ typedef uint32_t k_timeout_t;
#define K_MINUTES(m) K_SECONDS((m) * 60)
#define K_HOURS(h) K_MINUTES((h) * 60)
/* Defer work onto iso_task. In this port K_NO_WAIT runs the handler INLINE
* (timer.c), which fires e.g. GATT indications before the CP write response. */
#define K_NO_WAIT_ASYNC K_MSEC(1)
struct k_work;
typedef void (*k_work_handler_t)(struct k_work *work);
@@ -211,6 +223,8 @@ struct k_work {
k_work_handler_t handler;
int64_t timeout_us;
void *user_data;
uint32_t gen; /* Bumped on (re)schedule/cancel/deinit so a timer event
* queued before the change is skipped (see timer.c). */
};
struct k_work_sync {

Some files were not shown because too many files have changed in this diff Show More