diff --git a/doc/releases/release-notes-4.0.rst b/doc/releases/release-notes-4.0.rst index a8189fb7055..46cf5d8eafc 100644 --- a/doc/releases/release-notes-4.0.rst +++ b/doc/releases/release-notes-4.0.rst @@ -78,6 +78,8 @@ Bluetooth * Audio + * :c:func:`bt_tbs_client_register_cb` now supports multiple listeners and may now return an error. + * Host * Added API :c:func:`bt_gatt_get_uatt_mtu` to get current Unenhanced ATT MTU of a given diff --git a/include/zephyr/bluetooth/audio/tbs.h b/include/zephyr/bluetooth/audio/tbs.h index 6c0451fb616..7e64c34592b 100644 --- a/include/zephyr/bluetooth/audio/tbs.h +++ b/include/zephyr/bluetooth/audio/tbs.h @@ -30,6 +30,7 @@ #include #include +#include #include #ifdef __cplusplus @@ -680,6 +681,9 @@ struct bt_tbs_client_cb { /** Bearer friendly name has been read */ bt_tbs_client_read_string_cb friendly_name; #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ + + /** @internal Internally used field for list handling */ + sys_snode_t _node; }; /** @@ -998,8 +1002,12 @@ int bt_tbs_client_read_optional_opcodes(struct bt_conn *conn, * @brief Register the callbacks for CCP. * * @param cbs Pointer to the callback structure. + * + * @retval 0 Success + * @retval -EINVAL @p cbs is NULL + * @retval -EEXIST @p cbs is already registered */ -void bt_tbs_client_register_cb(const struct bt_tbs_client_cb *cbs); +int bt_tbs_client_register_cb(struct bt_tbs_client_cb *cbs); /** * @brief Look up Telephone Bearer Service instance by CCID diff --git a/samples/bluetooth/hap_ha/src/ccp_call_ctrl.c b/samples/bluetooth/hap_ha/src/ccp_call_ctrl.c index 8b7eda13c58..dcf1c4dffb4 100644 --- a/samples/bluetooth/hap_ha/src/ccp_call_ctrl.c +++ b/samples/bluetooth/hap_ha/src/ccp_call_ctrl.c @@ -155,7 +155,5 @@ struct bt_tbs_client_cb tbs_client_cb = { int ccp_call_ctrl_init(void) { - bt_tbs_client_register_cb(&tbs_client_cb); - - return 0; + return bt_tbs_client_register_cb(&tbs_client_cb); } diff --git a/samples/bluetooth/tmap_peripheral/src/ccp_call_ctrl.c b/samples/bluetooth/tmap_peripheral/src/ccp_call_ctrl.c index 97d9c64e919..a7f0637bcde 100644 --- a/samples/bluetooth/tmap_peripheral/src/ccp_call_ctrl.c +++ b/samples/bluetooth/tmap_peripheral/src/ccp_call_ctrl.c @@ -124,7 +124,11 @@ int ccp_call_ctrl_init(struct bt_conn *conn) int err; default_conn = bt_conn_ref(conn); - bt_tbs_client_register_cb(&tbs_client_cb); + err = bt_tbs_client_register_cb(&tbs_client_cb); + if (err != 0) { + return err; + } + err = bt_tbs_client_discover(conn); if (err != 0) { return err; diff --git a/subsys/bluetooth/audio/tbs_client.c b/subsys/bluetooth/audio/tbs_client.c index 71c34f781fc..44771207f3f 100644 --- a/subsys/bluetooth/audio/tbs_client.c +++ b/subsys/bluetooth/audio/tbs_client.c @@ -1,7 +1,7 @@ /* Bluetooth TBS - Telephone Bearer Service - Client * * Copyright (c) 2020 Bose Corporation - * Copyright (c) 2021 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,7 @@ struct bt_tbs_server_inst { struct bt_tbs_instance *current_inst; }; -static const struct bt_tbs_client_cb *tbs_client_cbs; +static sys_slist_t tbs_client_cbs = SYS_SLIST_STATIC_INIT(&tbs_client_cbs); static struct bt_tbs_server_inst srv_insts[CONFIG_BT_MAX_CONN]; @@ -245,6 +246,18 @@ static uint8_t net_buf_pull_call(struct net_buf_simple *buf, return 0; } +static void current_calls_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint8_t call_count, const struct bt_tbs_client_call *calls) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->current_calls != NULL) { + listener->current_calls(conn, err, inst_index, call_count, calls); + } + } +} + static void bearer_list_current_calls(struct bt_conn *conn, const struct bt_tbs_instance *inst, struct net_buf_simple *buf) { @@ -276,9 +289,7 @@ static void bearer_list_current_calls(struct bt_conn *conn, const struct bt_tbs_ cnt++; } - if (tbs_client_cbs != NULL && tbs_client_cbs->current_calls != NULL) { - tbs_client_cbs->current_calls(conn, 0, tbs_index(conn, inst), cnt, calls); - } + current_calls_changed(conn, 0, tbs_index(conn, inst), cnt, calls); } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ @@ -287,52 +298,58 @@ static void call_cp_callback_handler(struct bt_conn *conn, int err, uint8_t index, uint8_t opcode, uint8_t call_index) { - bt_tbs_client_cp_cb cp_cb = NULL; + struct bt_tbs_client_cb *listener, *next; LOG_DBG("Status: %s for the %s opcode for call 0x%02x", bt_tbs_status_str(err), bt_tbs_opcode_str(opcode), call_index); - if (tbs_client_cbs == NULL) { - return; - } - - switch (opcode) { + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + switch (opcode) { #if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) - case BT_TBS_CALL_OPCODE_ACCEPT: - cp_cb = tbs_client_cbs->accept_call; - break; + case BT_TBS_CALL_OPCODE_ACCEPT: + if (listener->accept_call != NULL) { + listener->accept_call(conn, err, index, call_index); + } + break; #endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ #if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) - case BT_TBS_CALL_OPCODE_TERMINATE: - cp_cb = tbs_client_cbs->terminate_call; - break; + case BT_TBS_CALL_OPCODE_TERMINATE: + if (listener->terminate_call != NULL) { + listener->terminate_call(conn, err, index, call_index); + } + break; #endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ #if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) - case BT_TBS_CALL_OPCODE_HOLD: - cp_cb = tbs_client_cbs->hold_call; - break; + case BT_TBS_CALL_OPCODE_HOLD: + if (listener->hold_call != NULL) { + listener->hold_call(conn, err, index, call_index); + } + break; #endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ #if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) - case BT_TBS_CALL_OPCODE_RETRIEVE: - cp_cb = tbs_client_cbs->retrieve_call; - break; + case BT_TBS_CALL_OPCODE_RETRIEVE: + if (listener->retrieve_call != NULL) { + listener->retrieve_call(conn, err, index, call_index); + } + break; #endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ #if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) - case BT_TBS_CALL_OPCODE_ORIGINATE: - cp_cb = tbs_client_cbs->originate_call; - break; + case BT_TBS_CALL_OPCODE_ORIGINATE: + if (listener->originate_call != NULL) { + listener->originate_call(conn, err, index, call_index); + } + break; #endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ #if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) - case BT_TBS_CALL_OPCODE_JOIN: - cp_cb = tbs_client_cbs->join_calls; - break; + case BT_TBS_CALL_OPCODE_JOIN: + if (listener->join_calls != NULL) { + listener->join_calls(conn, err, index, call_index); + } + break; #endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ - default: - break; - } - - if (cp_cb != 0) { - cp_cb(conn, err, index, call_index); + default: + break; + } } } #endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ @@ -353,6 +370,18 @@ const char *parse_string_value(const void *data, uint16_t length, } #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void provider_name_changed(struct bt_conn *conn, int err, uint8_t inst_index, + const char *name) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->bearer_provider_name != NULL) { + listener->bearer_provider_name(conn, err, inst_index, name); + } + } +} + static void provider_name_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -362,13 +391,23 @@ static void provider_name_notify_handler(struct bt_conn *conn, LOG_DBG("%s", name); - if (tbs_client_cbs != NULL && tbs_client_cbs->bearer_provider_name != NULL) { - tbs_client_cbs->bearer_provider_name(conn, 0, tbs_index(conn, tbs_inst), name); - } + provider_name_changed(conn, 0, tbs_index(conn, tbs_inst), name); } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ #if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) +static void technology_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint8_t technology) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->technology != NULL) { + listener->technology(conn, err, inst_index, technology); + } + } +} + static void technology_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -381,14 +420,24 @@ static void technology_notify_handler(struct bt_conn *conn, (void)memcpy(&technology, data, length); LOG_DBG("%s (0x%02x)", bt_tbs_technology_str(technology), technology); - if (tbs_client_cbs != NULL && tbs_client_cbs->technology != NULL) { - tbs_client_cbs->technology(conn, 0, tbs_index(conn, tbs_inst), technology); - } + technology_changed(conn, 0, tbs_index(conn, tbs_inst), technology); } } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ #if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) +static void signal_strength_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint8_t signal_strength) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->signal_strength != NULL) { + listener->signal_strength(conn, err, inst_index, signal_strength); + } + } +} + static void signal_strength_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -401,10 +450,7 @@ static void signal_strength_notify_handler(struct bt_conn *conn, (void)memcpy(&signal_strength, data, length); LOG_DBG("0x%02x", signal_strength); - if (tbs_client_cbs != NULL && tbs_client_cbs->signal_strength != NULL) { - tbs_client_cbs->signal_strength(conn, 0, tbs_index(conn, tbs_inst), - signal_strength); - } + signal_strength_changed(conn, 0, tbs_index(conn, tbs_inst), signal_strength); } } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ @@ -427,6 +473,18 @@ static void current_calls_notify_handler(struct bt_conn *conn, #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ #if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) +static void status_flags_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint16_t status_flags) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->status_flags != NULL) { + listener->status_flags(conn, err, inst_index, status_flags); + } + } +} + static void status_flags_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -438,15 +496,25 @@ static void status_flags_notify_handler(struct bt_conn *conn, if (length == sizeof(status_flags)) { (void)memcpy(&status_flags, data, length); LOG_DBG("0x%04x", status_flags); - if (tbs_client_cbs != NULL && tbs_client_cbs->status_flags != NULL) { - tbs_client_cbs->status_flags(conn, 0, tbs_index(conn, tbs_inst), - status_flags); - } + + status_flags_changed(conn, 0, tbs_index(conn, tbs_inst), status_flags); } } #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) +static void call_uri_changed(struct bt_conn *conn, int err, uint8_t inst_index, + const char *call_uri) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->call_uri != NULL) { + listener->call_uri(conn, err, inst_index, call_uri); + } + } +} + static void incoming_uri_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -456,12 +524,23 @@ static void incoming_uri_notify_handler(struct bt_conn *conn, LOG_DBG("%s", uri); - if (tbs_client_cbs != NULL && tbs_client_cbs->call_uri != NULL) { - tbs_client_cbs->call_uri(conn, 0, tbs_index(conn, tbs_inst), uri); - } + call_uri_changed(conn, 0, tbs_index(conn, tbs_inst), uri); } #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ +static void call_state_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint8_t call_count, + const struct bt_tbs_client_call_state *call_states) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->call_state != NULL) { + listener->call_state(conn, err, inst_index, call_count, call_states); + } + } +} + static void call_state_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -496,9 +575,7 @@ static void call_state_notify_handler(struct bt_conn *conn, cnt++; } - if (tbs_client_cbs != NULL && tbs_client_cbs->call_state != NULL) { - tbs_client_cbs->call_state(conn, 0, tbs_index(conn, tbs_inst), cnt, call_states); - } + call_state_changed(conn, 0, tbs_index(conn, tbs_inst), cnt, call_states); } #if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) @@ -522,6 +599,19 @@ static void call_cp_notify_handler(struct bt_conn *conn, } #endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ +static void terminate_reason_changed(struct bt_conn *conn, int err, uint8_t inst_index, + struct bt_tbs_terminate_reason reason) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->termination_reason != NULL) { + listener->termination_reason(conn, err, inst_index, reason.call_index, + reason.reason); + } + } +} + static void termination_reason_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -535,14 +625,23 @@ static void termination_reason_notify_handler(struct bt_conn *conn, LOG_DBG("ID 0x%02X, reason %s", reason.call_index, bt_tbs_term_reason_str(reason.reason)); - if (tbs_client_cbs != NULL && tbs_client_cbs->termination_reason != NULL) { - tbs_client_cbs->termination_reason(conn, 0, tbs_index(conn, tbs_inst), - reason.call_index, reason.reason); - } + terminate_reason_changed(conn, 0, tbs_index(conn, tbs_inst), reason); } } #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) +static void remote_uri_changed(struct bt_conn *conn, int err, uint8_t inst_index, + const char *remote_uri) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->remote_uri != NULL) { + listener->remote_uri(conn, err, inst_index, remote_uri); + } + } +} + static void in_call_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -552,13 +651,23 @@ static void in_call_notify_handler(struct bt_conn *conn, LOG_DBG("%s", uri); - if (tbs_client_cbs != NULL && tbs_client_cbs->remote_uri != NULL) { - tbs_client_cbs->remote_uri(conn, 0, tbs_index(conn, tbs_inst), uri); - } + remote_uri_changed(conn, 0, tbs_index(conn, tbs_inst), uri); } #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ #if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) +static void friendly_name_changed(struct bt_conn *conn, int err, uint8_t inst_index, + const char *friendly_name) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->friendly_name != NULL) { + listener->friendly_name(conn, err, inst_index, friendly_name); + } + } +} + static void friendly_name_notify_handler(struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, const void *data, uint16_t length) @@ -568,9 +677,7 @@ static void friendly_name_notify_handler(struct bt_conn *conn, LOG_DBG("%s", name); - if (tbs_client_cbs != NULL && tbs_client_cbs->friendly_name != NULL) { - tbs_client_cbs->friendly_name(conn, 0, tbs_index(conn, tbs_inst), name); - } + friendly_name_changed(conn, 0, tbs_index(conn, tbs_inst), name); } #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ @@ -713,14 +820,17 @@ static uint8_t inst_cnt(struct bt_tbs_server_inst *srv_inst) static void tbs_client_discover_complete(struct bt_conn *conn, int err) { struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; + struct bt_tbs_client_cb *listener, *next; LOG_DBG("conn %p err %d", (void *)conn, err); /* Clear the current instance in discovery */ srv_inst->current_inst = NULL; - if (tbs_client_cbs != NULL && tbs_client_cbs->discover != NULL) { - tbs_client_cbs->discover(conn, err, inst_cnt(srv_inst), gtbs_found(srv_inst)); + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->discover != NULL) { + listener->discover(conn, err, inst_cnt(srv_inst), gtbs_found(srv_inst)); + } } } @@ -730,105 +840,48 @@ static void tbs_client_discover_complete(struct bt_conn *conn, int err) defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) || \ defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) || \ defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) -/* Common function to read tbs_client strings which may require long reads */ -static uint8_t handle_string_long_read(struct bt_conn *conn, uint8_t err, - struct bt_gatt_read_params *params, - const void *data, - uint16_t length, - bt_tbs_client_read_string_cb cb, - bool truncatable) + +static bool can_add_string_to_net_buf(const struct net_buf_simple *buf, size_t len) { - struct bt_tbs_instance *inst = CONTAINER_OF(params, - struct bt_tbs_instance, - read_params); - uint16_t offset = params->single.offset; - uint8_t inst_index = tbs_index(conn, inst); - const char *received_string; - int tbs_err = err; + return buf->len + len + sizeof('\0') <= buf->size; +} - if ((tbs_err == 0) && (data != NULL) && - (net_buf_simple_tailroom(&inst->net_buf) < length)) { - LOG_DBG("Read length %u: String buffer full", length); - if (truncatable) { - /* Use the remaining buffer and continue reading */ - LOG_DBG("Truncating string"); - length = net_buf_simple_tailroom(&inst->net_buf); - } else { - tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; - } - } +/* Common function to read tbs_client strings which may require long reads */ +static uint8_t handle_string_long_read(struct bt_tbs_instance *inst, uint8_t err, const void *data, + uint16_t offset, uint16_t length, bool truncatable) +{ + if (err != 0) { + LOG_DBG("err: %u", err); - if (tbs_err != 0) { - LOG_DBG("err: %d", tbs_err); - - tbs_client_gatt_read_complete(inst); - - if (cb != NULL) { - cb(conn, tbs_err, inst_index, NULL); - } - - return BT_GATT_ITER_STOP; + return BT_GATT_ERR(err); } if (data != NULL) { /* Get data and try to read more using read long procedure */ LOG_DBG("Read (offset %u): %s", offset, bt_hex(data, length)); - net_buf_simple_add_mem(&inst->net_buf, data, length); - - return BT_GATT_ITER_CONTINUE; - } - - if (inst->net_buf.len == 0) { - received_string = NULL; - } else { - uint16_t str_length = inst->net_buf.len; - - /* Ensure there is space for string termination */ - if (net_buf_simple_tailroom(&inst->net_buf) < 1) { - LOG_DBG("Truncating string"); + if (!can_add_string_to_net_buf(&inst->net_buf, length)) { + LOG_DBG("Read length %u: String buffer full", length); if (truncatable) { - /* Truncate */ - str_length--; - } else { - tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; - } - } - - if (tbs_err == 0) { - char *str_data; - - /* Get a reference to the string buffer */ - str_data = net_buf_simple_pull_mem(&inst->net_buf, - inst->net_buf.len); - - /* All strings are UTF-8, truncate properly if needed */ - str_data[str_length] = '\0'; - received_string = utf8_trunc(str_data); - - /* The string might have been truncated */ - if (strlen(received_string) < str_length) { + /* Use the remaining buffer and stop reading and leave room for NULL + * terminator + */ LOG_DBG("Truncating string"); - if (!truncatable) { - tbs_err = - BT_ATT_ERR_INSUFFICIENT_RESOURCES; - } + length = net_buf_simple_tailroom(&inst->net_buf) - sizeof('\0'); + net_buf_simple_add_mem(&inst->net_buf, data, length); + + /* Ensure that the data is correctly truncated */ + utf8_trunc(inst->net_buf.data); + } else { + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); } + } else { + net_buf_simple_add_mem(&inst->net_buf, data, length); - LOG_DBG("%s", received_string); + return BT_GATT_ITER_CONTINUE; } } - if (tbs_err) { - received_string = NULL; - } - - tbs_client_gatt_read_complete(inst); - - if (cb != NULL) { - cb(conn, tbs_err, inst_index, received_string); - } - return BT_GATT_ITER_STOP; } #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME || \ @@ -871,38 +924,67 @@ static uint8_t read_bearer_provider_name_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { - bt_tbs_client_read_string_cb cb = NULL; + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; - LOG_DBG("Read bearer provider name"); + LOG_DBG(""); - if (tbs_client_cbs != NULL && - tbs_client_cbs->bearer_provider_name != NULL) { - cb = tbs_client_cbs->bearer_provider_name; + ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); + if (ret != BT_GATT_ITER_CONTINUE) { + if (ret == BT_GATT_ITER_STOP) { + /* At this point the inst->net_buf.data contains a NULL terminator string */ + provider_name_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } else { + provider_name_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; } - return handle_string_long_read(conn, err, params, data, - length, cb, true); + return BT_GATT_ITER_CONTINUE; } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ #if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void bearer_uci_changed(struct bt_conn *conn, int err, uint8_t inst_index, const char *uci) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->bearer_uci != NULL) { + listener->bearer_uci(conn, err, inst_index, uci); + } + } +} + static uint8_t read_bearer_uci_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { - bt_tbs_client_read_string_cb cb = NULL; + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; - LOG_DBG("Read bearer UCI"); + LOG_DBG(""); - if (tbs_client_cbs != NULL && tbs_client_cbs->bearer_uci != NULL) { - cb = tbs_client_cbs->bearer_uci; + ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); + if (ret != BT_GATT_ITER_CONTINUE) { + if (ret == BT_GATT_ITER_STOP) { + /* At this point the inst->net_buf.data contains a NULL terminator string */ + bearer_uci_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } else { + bearer_uci_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; } - /* The specification does not indicate truncation as an option, so - * fail if insufficient buffer space. - */ - return handle_string_long_read(conn, err, params, data, - length, cb, false); + return BT_GATT_ITER_CONTINUE; } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ @@ -935,29 +1017,50 @@ static uint8_t read_technology_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && tbs_client_cbs->technology != NULL) { - tbs_client_cbs->technology(conn, cb_err, inst_index, technology); - } + technology_changed(conn, cb_err, inst_index, technology); return BT_GATT_ITER_STOP; } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ #if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) +static void uri_list_changed(struct bt_conn *conn, int err, uint8_t inst_index, + const char *uri_list) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->uri_list != NULL) { + listener->uri_list(conn, err, inst_index, uri_list); + } + } +} + static uint8_t read_uri_list_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { - bt_tbs_client_read_string_cb cb = NULL; + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; - LOG_DBG("Read bearer UCI"); + LOG_DBG(""); - if (tbs_client_cbs != NULL && tbs_client_cbs->uri_list != NULL) { - cb = tbs_client_cbs->uri_list; + ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); + if (ret != BT_GATT_ITER_CONTINUE) { + if (ret == BT_GATT_ITER_STOP) { + /* At this point the inst->net_buf.data contains a NULL terminator string */ + uri_list_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } else { + uri_list_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; } - return handle_string_long_read(conn, err, params, data, - length, cb, false); + return BT_GATT_ITER_CONTINUE; } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ @@ -990,16 +1093,25 @@ static uint8_t read_signal_strength_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && tbs_client_cbs->signal_strength != NULL) { - tbs_client_cbs->signal_strength(conn, cb_err, inst_index, - signal_strength); - } + signal_strength_changed(conn, cb_err, inst_index, signal_strength); return BT_GATT_ITER_STOP; } #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ #if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) +static void signal_interval_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint8_t signal_interval) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->signal_interval != NULL) { + listener->signal_interval(conn, err, inst_index, signal_interval); + } + } +} + static uint8_t read_signal_interval_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) @@ -1028,10 +1140,7 @@ static uint8_t read_signal_interval_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs && tbs_client_cbs->signal_interval) { - tbs_client_cbs->signal_interval(conn, cb_err, inst_index, - signal_interval); - } + signal_interval_changed(conn, cb_err, inst_index, signal_interval); return BT_GATT_ITER_STOP; } @@ -1060,11 +1169,7 @@ static uint8_t read_current_calls_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && - tbs_client_cbs->current_calls != NULL) { - tbs_client_cbs->current_calls(conn, tbs_err, - inst_index, 0, NULL); - } + current_calls_changed(conn, tbs_err, inst_index, 0, NULL); return BT_GATT_ITER_STOP; } @@ -1085,11 +1190,7 @@ static uint8_t read_current_calls_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); if (inst->net_buf.len == 0) { - if (tbs_client_cbs != NULL && - tbs_client_cbs->current_calls != NULL) { - tbs_client_cbs->current_calls(conn, 0, - inst_index, 0, NULL); - } + current_calls_changed(conn, 0, inst_index, 0, NULL); return BT_GATT_ITER_STOP; } @@ -1101,6 +1202,17 @@ static uint8_t read_current_calls_cb(struct bt_conn *conn, uint8_t err, #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ #if defined(CONFIG_BT_TBS_CLIENT_CCID) +static void ccid_changed(struct bt_conn *conn, int err, uint8_t inst_index, uint8_t ccid) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->ccid != NULL) { + listener->ccid(conn, err, inst_index, ccid); + } + } +} + static uint8_t read_ccid_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) @@ -1129,9 +1241,7 @@ static uint8_t read_ccid_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && tbs_client_cbs->ccid != NULL) { - tbs_client_cbs->ccid(conn, cb_err, inst_index, ccid); - } + ccid_changed(conn, cb_err, inst_index, ccid); return BT_GATT_ITER_STOP; } @@ -1166,11 +1276,7 @@ static uint8_t read_status_flags_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && - tbs_client_cbs->status_flags != NULL) { - tbs_client_cbs->status_flags(conn, cb_err, inst_index, - status_flags); - } + status_flags_changed(conn, cb_err, inst_index, status_flags); return BT_GATT_ITER_STOP; } @@ -1181,16 +1287,27 @@ static uint8_t read_call_uri_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { - bt_tbs_client_read_string_cb cb = NULL; + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; - LOG_DBG("Read incoming call target bearer URI"); + LOG_DBG(""); - if (tbs_client_cbs != NULL && tbs_client_cbs->call_uri != NULL) { - cb = tbs_client_cbs->call_uri; + ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); + if (ret != BT_GATT_ITER_CONTINUE) { + if (ret == BT_GATT_ITER_STOP) { + /* At this point the inst->net_buf.data contains a NULL terminator string */ + call_uri_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } else { + call_uri_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; } - return handle_string_long_read(conn, err, params, data, - length, cb, false); + return BT_GATT_ITER_CONTINUE; } #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ @@ -1218,11 +1335,7 @@ static uint8_t read_call_state_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && - tbs_client_cbs->call_state != NULL) { - tbs_client_cbs->call_state(conn, tbs_err, - inst_index, 0, NULL); - } + call_state_changed(conn, tbs_err, inst_index, 0, NULL); return BT_GATT_ITER_STOP; } @@ -1240,10 +1353,7 @@ static uint8_t read_call_state_cb(struct bt_conn *conn, uint8_t err, if (inst->net_buf.len == 0) { tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && - tbs_client_cbs->call_state != NULL) { - tbs_client_cbs->call_state(conn, 0, inst_index, 0, NULL); - } + call_state_changed(conn, 0, inst_index, 0, NULL); return BT_GATT_ITER_STOP; } @@ -1270,14 +1380,24 @@ static uint8_t read_call_state_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && tbs_client_cbs->call_state != NULL) { - tbs_client_cbs->call_state(conn, tbs_err, inst_index, cnt, call_states); - } + call_state_changed(conn, tbs_err, inst_index, cnt, call_states); return BT_GATT_ITER_STOP; } #if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) +static void optional_opcodes_changed(struct bt_conn *conn, int err, uint8_t inst_index, + uint16_t optional_opcodes) +{ + struct bt_tbs_client_cb *listener, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { + if (listener->optional_opcodes != NULL) { + listener->optional_opcodes(conn, err, inst_index, optional_opcodes); + } + } +} + static uint8_t read_optional_opcodes_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) @@ -1304,10 +1424,7 @@ static uint8_t read_optional_opcodes_cb(struct bt_conn *conn, uint8_t err, tbs_client_gatt_read_complete(inst); - if (tbs_client_cbs != NULL && - tbs_client_cbs->optional_opcodes != NULL) { - tbs_client_cbs->optional_opcodes(conn, cb_err, inst_index, optional_opcodes); - } + optional_opcodes_changed(conn, cb_err, inst_index, optional_opcodes); return BT_GATT_ITER_STOP; } @@ -1318,17 +1435,27 @@ static uint8_t read_remote_uri_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { - bt_tbs_client_read_string_cb cb = NULL; + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; - LOG_DBG("Read incoming call URI"); + LOG_DBG(""); - if (tbs_client_cbs != NULL && - tbs_client_cbs->remote_uri != NULL) { - cb = tbs_client_cbs->remote_uri; + ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); + if (ret != BT_GATT_ITER_CONTINUE) { + if (ret == BT_GATT_ITER_STOP) { + /* At this point the inst->net_buf.data contains a NULL terminator string */ + remote_uri_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } else { + remote_uri_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; } - return handle_string_long_read(conn, err, params, data, - length, cb, false); + return BT_GATT_ITER_CONTINUE; } #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ @@ -1337,17 +1464,27 @@ static uint8_t read_friendly_name_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, const void *data, uint16_t length) { - bt_tbs_client_read_string_cb cb = NULL; + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; - LOG_DBG("Read incoming call target bearer URI"); + LOG_DBG(""); - if (tbs_client_cbs != NULL && - tbs_client_cbs->friendly_name != NULL) { - cb = tbs_client_cbs->friendly_name; + ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); + if (ret != BT_GATT_ITER_CONTINUE) { + if (ret == BT_GATT_ITER_STOP) { + /* At this point the inst->net_buf.data contains a NULL terminator string */ + friendly_name_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } else { + friendly_name_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; } - return handle_string_long_read(conn, err, params, data, - length, cb, true); + return BT_GATT_ITER_CONTINUE; } #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ @@ -2272,9 +2409,21 @@ int bt_tbs_client_discover(struct bt_conn *conn) #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ } -void bt_tbs_client_register_cb(const struct bt_tbs_client_cb *cbs) +int bt_tbs_client_register_cb(struct bt_tbs_client_cb *cb) { - tbs_client_cbs = cbs; + CHECKIF(cb == NULL) { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (sys_slist_find(&tbs_client_cbs, &cb->_node, NULL)) { + return -EEXIST; + } + + sys_slist_append(&tbs_client_cbs, &cb->_node); + + return 0; } #if defined(CONFIG_BT_TBS_CLIENT_CCID) diff --git a/subsys/bluetooth/audio/tbs_internal.h b/subsys/bluetooth/audio/tbs_internal.h index cc5acebb4cf..e066cc1de04 100644 --- a/subsys/bluetooth/audio/tbs_internal.h +++ b/subsys/bluetooth/audio/tbs_internal.h @@ -301,7 +301,7 @@ struct bt_tbs_in_uri { defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) || \ defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) || \ defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) -#define BT_TBS_CLIENT_INST_READ_BUF_SIZE (BT_ATT_MAX_ATTRIBUTE_LEN) +#define BT_TBS_CLIENT_INST_READ_BUF_SIZE (BT_ATT_MAX_ATTRIBUTE_LEN + 1 /* NULL terminator*/) #else /* Need only be the size of call state reads which is the largest of the * remaining characteristic values diff --git a/tests/bluetooth/tester/src/audio/btp_ccp.c b/tests/bluetooth/tester/src/audio/btp_ccp.c index bd58dea341c..5bd9a28458f 100644 --- a/tests/bluetooth/tester/src/audio/btp_ccp.c +++ b/tests/bluetooth/tester/src/audio/btp_ccp.c @@ -277,7 +277,7 @@ static void tbs_client_cp_cb(struct bt_conn *conn, int err, uint8_t inst_index, tbs_client_cp_ev(conn, err); } -static const struct bt_tbs_client_cb tbs_client_callbacks = { +static struct bt_tbs_client_cb tbs_client_callbacks = { .discover = tbs_client_discover_cb, .originate_call = tbs_client_cp_cb, .terminate_call = tbs_client_cp_cb, @@ -860,10 +860,15 @@ static const struct btp_handler ccp_handlers[] = { uint8_t tester_init_ccp(void) { + int err; + tester_register_command_handlers(BTP_SERVICE_ID_CCP, ccp_handlers, ARRAY_SIZE(ccp_handlers)); - bt_tbs_client_register_cb(&tbs_client_callbacks); + err = bt_tbs_client_register_cb(&tbs_client_callbacks); + if (err != 0) { + return BTP_STATUS_FAILED; + } return BTP_STATUS_SUCCESS; } diff --git a/tests/bsim/bluetooth/audio/src/tbs_client_test.c b/tests/bsim/bluetooth/audio/src/tbs_client_test.c index 2ed1a81c037..66a4574647b 100644 --- a/tests/bsim/bluetooth/audio/src/tbs_client_test.c +++ b/tests/bsim/bluetooth/audio/src/tbs_client_test.c @@ -303,7 +303,7 @@ static void tbs_client_term_reason_cb(struct bt_conn *conn, SET_FLAG(term_reason); } -static const struct bt_tbs_client_cb tbs_client_cbs = { +static struct bt_tbs_client_cb tbs_client_cbs = { .discover = tbs_client_discover_cb, .originate_call = tbs_client_originate_call_cb, .terminate_call = tbs_client_terminate_call_cb, @@ -492,7 +492,12 @@ static void test_main(void) } bt_conn_cb_register(&conn_callbacks); - bt_tbs_client_register_cb(&tbs_client_cbs); + + err = bt_tbs_client_register_cb(&tbs_client_cbs); + if (err != 0) { + FAIL("Failed to register TBS client cbs (err %d)\n", err); + return; + } WAIT_FOR_COND(bt_init);