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

This commit is contained in:
Liu Linyan
2026-08-04 14:07:10 +08:00
committed by Linyan Liu
parent 6d980d673f
commit af9ae1c0aa
113 changed files with 4547 additions and 2104 deletions
@@ -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
*/