From 8495dd1246d0f6991923c6d5bdd2eeaaae6f4c36 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Thu, 10 Oct 2024 11:03:40 +0200 Subject: [PATCH 1/2] Bluetooth: CCP: Server: Add support for get UCI Add support for getting the UCI. For now the UCI will be duplicated by the TBS implementation, but will be optimizied in the future so only one copy of the UCI exists. Signed-off-by: Emil Gydesen --- .../bluetooth/shell/audio/ccp.rst | 10 ++++ include/zephyr/bluetooth/audio/ccp.h | 13 +++++ include/zephyr/bluetooth/audio/tbs.h | 9 ++- .../bluetooth/audio/ccp_call_control_server.c | 28 ++++++++++ .../audio/shell/ccp_call_control_server.c | 28 ++++++++++ subsys/bluetooth/audio/tbs_internal.h | 1 - .../audio/ccp_call_control_server/src/main.c | 55 ++++++++++++++++++- 7 files changed, 141 insertions(+), 3 deletions(-) diff --git a/doc/connectivity/bluetooth/shell/audio/ccp.rst b/doc/connectivity/bluetooth/shell/audio/ccp.rst index e5f81db914ad7..aed5eaa3b8336 100644 --- a/doc/connectivity/bluetooth/shell/audio/ccp.rst +++ b/doc/connectivity/bluetooth/shell/audio/ccp.rst @@ -24,6 +24,7 @@ to :code:`0` which is the GTBS bearer. init : Initialize CCP Call Control Server set_bearer_name : Set bearer name [index] get_bearer_name : Get bearer name [index] + get_bearer_name : Get bearer UCI [index] Example Usage @@ -58,6 +59,15 @@ Setting and getting the bearer name uart:~$ ccp_call_control_server get_bearer_name 1 Bearer[1] name: New TBS name +Getting the bearer UCI +---------------------- + +.. code-block:: console + + uart:~$ ccp_call_control_server get_bearer_uci + Bearer[0] UCI: un999 + uart:~$ ccp_call_control_server get_bearer_name 1 + Bearer[1] UCI: skype Call Control Client ******************* diff --git a/include/zephyr/bluetooth/audio/ccp.h b/include/zephyr/bluetooth/audio/ccp.h index 0dd68befe2961..b8c5d03d387c4 100644 --- a/include/zephyr/bluetooth/audio/ccp.h +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -120,6 +120,19 @@ int bt_ccp_call_control_server_set_bearer_provider_name( int bt_ccp_call_control_server_get_bearer_provider_name( struct bt_ccp_call_control_server_bearer *bearer, const char **name); +/** + * @brief Get the bearer provider name. + * + * @param[in] bearer The bearer to get the name for. + * @param[out] uci Pointer that will be updated to be the bearer uci. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p uci is NULL + * @retval -EFAULT @p bearer is not registered + */ +int bt_ccp_call_control_server_get_bearer_uci(struct bt_ccp_call_control_server_bearer *bearer, + const char **uci); + /** @} */ /* End of group bt_ccp_call_control_server */ /** diff --git a/include/zephyr/bluetooth/audio/tbs.h b/include/zephyr/bluetooth/audio/tbs.h index e1cc6bd36af15..2d8eb8338a829 100644 --- a/include/zephyr/bluetooth/audio/tbs.h +++ b/include/zephyr/bluetooth/audio/tbs.h @@ -199,7 +199,14 @@ extern "C" { * whenever the client should perform on action on the GTBS instance of the * server, rather than any of the specific Telephone Bearer Service instances. */ -#define BT_TBS_GTBS_INDEX 0xFF +#define BT_TBS_GTBS_INDEX 0xFF + +/** Maximum size of bearer uniform caller identifier (UCI) + * + * Includes the NULL terminator. + * Allowed values are defined by Bluetooth Assigned Numbers. + */ +#define BT_TBS_MAX_UCI_SIZE 6 /** @brief Opaque Telephone Bearer Service instance. */ struct bt_tbs_instance; diff --git a/subsys/bluetooth/audio/ccp_call_control_server.c b/subsys/bluetooth/audio/ccp_call_control_server.c index 053a83ed6bfff..37f766bfc1596 100644 --- a/subsys/bluetooth/audio/ccp_call_control_server.c +++ b/subsys/bluetooth/audio/ccp_call_control_server.c @@ -23,6 +23,7 @@ LOG_MODULE_REGISTER(bt_ccp_call_control_server, CONFIG_BT_CCP_CALL_CONTROL_SERVE /* A service instance can either be a GTBS or a TBS instance */ struct bt_ccp_call_control_server_bearer { char provider_name[CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH + 1]; + char uci[BT_TBS_MAX_UCI_SIZE]; uint8_t tbs_index; bool registered; }; @@ -75,6 +76,7 @@ int bt_ccp_call_control_server_register_bearer(const struct bt_tbs_register_para free_bearer->tbs_index = (uint8_t)ret; (void)utf8_lcpy(free_bearer->provider_name, param->provider_name, sizeof(free_bearer->provider_name)); + (void)utf8_lcpy(free_bearer->uci, param->uci, sizeof(free_bearer->uci)); *bearer = free_bearer; return 0; @@ -175,3 +177,29 @@ int bt_ccp_call_control_server_get_bearer_provider_name( return 0; } + +int bt_ccp_call_control_server_get_bearer_uci(struct bt_ccp_call_control_server_bearer *bearer, + const char **uci) +{ + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + CHECKIF(uci == NULL) { + LOG_DBG("uci is NULL"); + + return -EINVAL; + } + + if (!bearer->registered) { + LOG_DBG("Bearer %p not registered", bearer); + + return -EFAULT; + } + + *uci = bearer->uci; + + return 0; +} diff --git a/subsys/bluetooth/audio/shell/ccp_call_control_server.c b/subsys/bluetooth/audio/shell/ccp_call_control_server.c index 4934fe8b52c3d..0f9005a82cea5 100644 --- a/subsys/bluetooth/audio/shell/ccp_call_control_server.c +++ b/subsys/bluetooth/audio/shell/ccp_call_control_server.c @@ -155,6 +155,32 @@ static int cmd_ccp_call_control_server_get_bearer_name(const struct shell *sh, s return 0; } +static int cmd_ccp_call_control_server_get_bearer_uci(const struct shell *sh, size_t argc, + char *argv[]) +{ + unsigned long index = 0; + const char *uci; + int err = 0; + + if (argc > 1) { + index = validate_and_get_index(sh, argv[1]); + if (index < 0) { + return index; + } + } + + err = bt_ccp_call_control_server_get_bearer_uci(bearers[index], &uci); + if (err != 0) { + shell_error(sh, "Failed to get bearer[%lu] UCI: %d", index, err); + + return -ENOEXEC; + } + + shell_print(sh, "Bearer[%lu] UCI: %s", index, uci); + + return 0; +} + static int cmd_ccp_call_control_server(const struct shell *sh, size_t argc, char **argv) { if (argc > 1) { @@ -174,6 +200,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE(ccp_call_control_server_cmds, cmd_ccp_call_control_server_set_bearer_name, 2, 1), SHELL_CMD_ARG(get_bearer_name, NULL, "Get bearer name [index]", cmd_ccp_call_control_server_get_bearer_name, 1, 1), + SHELL_CMD_ARG(get_bearer_uci, NULL, "Get bearer UCI [index]", + cmd_ccp_call_control_server_get_bearer_uci, 1, 1), SHELL_SUBCMD_SET_END); SHELL_CMD_ARG_REGISTER(ccp_call_control_server, &ccp_call_control_server_cmds, diff --git a/subsys/bluetooth/audio/tbs_internal.h b/subsys/bluetooth/audio/tbs_internal.h index 08fadf48745fb..11a279efacb5d 100644 --- a/subsys/bluetooth/audio/tbs_internal.h +++ b/subsys/bluetooth/audio/tbs_internal.h @@ -21,7 +21,6 @@ #include #include -#define BT_TBS_MAX_UCI_SIZE 6 #define BT_TBS_MIN_URI_LEN 3 /* a:b */ #define BT_TBS_FREE_CALL_INDEX 0 diff --git a/tests/bluetooth/audio/ccp_call_control_server/src/main.c b/tests/bluetooth/audio/ccp_call_control_server/src/main.c index af09be763bb17..6ee008c035a12 100644 --- a/tests/bluetooth/audio/ccp_call_control_server/src/main.c +++ b/tests/bluetooth/audio/ccp_call_control_server/src/main.c @@ -27,6 +27,7 @@ DEFINE_FFF_GLOBALS; #define DEFAULT_BEARER_NAME "test" +#define DEFAULT_BEARER_UCI "un999" struct ccp_call_control_server_test_suite_fixture { /** Need 1 additional bearer than the max to trigger some corner cases */ @@ -84,7 +85,7 @@ static void register_default_bearer(struct ccp_call_control_server_test_suite_fi { const struct bt_tbs_register_param register_param = { .provider_name = DEFAULT_BEARER_NAME, - .uci = "un999", + .uci = DEFAULT_BEARER_UCI, .uri_schemes_supported = "tel", .gtbs = true, .authorization_required = false, @@ -409,3 +410,55 @@ static ZTEST_F(ccp_call_control_server_test_suite, err = bt_ccp_call_control_server_get_bearer_provider_name(fixture->bearers[0], NULL); zassert_equal(err, -EINVAL, "Unexpected return value %d", err); } + +static ZTEST_F(ccp_call_control_server_test_suite, test_bt_ccp_call_control_server_get_bearer_uci) +{ + const char *res_bearer_name; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_get_bearer_uci(fixture->bearers[0], &res_bearer_name); + zassert_equal(err, 0, "Unexpected return value %d", err); + + zassert_str_equal(DEFAULT_BEARER_UCI, res_bearer_name, "%s != %s", DEFAULT_BEARER_UCI, + res_bearer_name); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_bt_ccp_call_control_server_get_bearer_uci_inval_not_registered) +{ + const char *res_bearer_name; + int err; + + /* Register and unregister bearer to get a valid pointer but where it is unregistered*/ + register_default_bearer(fixture); + err = bt_ccp_call_control_server_unregister_bearer(fixture->bearers[0]); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_ccp_call_control_server_get_bearer_uci(fixture->bearers[0], &res_bearer_name); + zassert_equal(err, -EFAULT, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_bt_ccp_call_control_server_get_bearer_uci_inval_null_bearer) +{ + const char *res_bearer_name; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_get_bearer_uci(NULL, &res_bearer_name); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_bt_ccp_call_control_server_get_bearer_uci_inval_null_name) +{ + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_get_bearer_uci(fixture->bearers[0], NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} From 5b01daa07ef8823fc4469b63477683e52852be98 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Tue, 22 Oct 2024 13:07:53 +0200 Subject: [PATCH 2/2] Bluetooth: CCP: Client: Add support for get bearer uci Add support for getting the remote bearer UCI. Signed-off-by: Emil Gydesen --- .../bluetooth/shell/audio/ccp.rst | 4 + include/zephyr/bluetooth/audio/ccp.h | 32 +++++ .../ccp_call_control_client/prj.conf | 1 + .../ccp_call_control_client/src/main.c | 82 ++++++++++--- .../bluetooth/audio/ccp_call_control_client.c | 112 ++++++++++++++---- .../audio/shell/ccp_call_control_client.c | 49 ++++++++ .../include/ccp_call_control_client.h | 2 + .../audio/ccp_call_control_client/prj.conf | 2 + .../src/test_procedures.c | 50 ++++++++ .../uut/ccp_call_control_client.c | 7 ++ .../ccp_call_control_client/uut/tbs_client.c | 18 +++ .../audio/src/ccp_call_control_client_test.c | 60 ++++++++-- 12 files changed, 372 insertions(+), 47 deletions(-) diff --git a/doc/connectivity/bluetooth/shell/audio/ccp.rst b/doc/connectivity/bluetooth/shell/audio/ccp.rst index aed5eaa3b8336..bc18e000151e7 100644 --- a/doc/connectivity/bluetooth/shell/audio/ccp.rst +++ b/doc/connectivity/bluetooth/shell/audio/ccp.rst @@ -100,3 +100,7 @@ Example Usage when connected Bearer 0x20046254 name: Generic TBS uart:~$ ccp_call_control_client read_bearer_name 1 Bearer 0x20046256 name: Telephone Bearer #1 + uart:~$ ccp_call_control_client read_bearer_uci + Bearer 0x20046254 UCI: un999 + uart:~$ ccp_call_control_client read_bearer_uci 1 + Bearer 0x20046256 name: skype diff --git a/include/zephyr/bluetooth/audio/ccp.h b/include/zephyr/bluetooth/audio/ccp.h index b8c5d03d387c4..1746f55119397 100644 --- a/include/zephyr/bluetooth/audio/ccp.h +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -197,6 +197,21 @@ struct bt_ccp_call_control_client_cb { const char *name); #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + /** + * @brief Callback function for bt_ccp_call_control_client_read_bearer_provider_name(). + * + * This callback is called once the read bearer provider name procedure is completed. + * + * @param client Call Control Client instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param uci The UCI of the bearer. NULL if @p err is not 0. + */ + void (*bearer_uci)(struct bt_ccp_call_control_client_bearer *bearer, int err, + const char *uci); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + /** @cond INTERNAL_HIDDEN */ /** Internally used field for list handling */ sys_snode_t _node; @@ -275,6 +290,23 @@ int bt_ccp_call_control_client_get_bearers(struct bt_ccp_call_control_client *cl */ int bt_ccp_call_control_client_read_bearer_provider_name( struct bt_ccp_call_control_client_bearer *bearer); + +/** + * @brief Read the bearer Unicorm Caller Identifier (UCI) of a remote TBS bearer. + * + * @kconfig_dep{CONFIG_BT_TBS_CLIENT_BEARER_UCI} + * + * @param bearer The bearer to read the UCI from + * + * @retval 0 Success + * @retval -EINVAL @p bearer is NULL + * @retval -EFAULT @p bearer has not been discovered + * @retval -EEXIST A @ref bt_ccp_call_control_client could not be identified for @p bearer + * @retval -EBUSY The @ref bt_ccp_call_control_client identified by @p bearer is busy, or the TBS + * instance of @p bearer is busy. + * @retval -ENOTCONN The @ref bt_ccp_call_control_client identified by @p bearer is not connected + */ +int bt_ccp_call_control_client_read_bearer_uci(struct bt_ccp_call_control_client_bearer *bearer); /** @} */ /* End of group bt_ccp_call_control_client */ #ifdef __cplusplus } diff --git a/samples/bluetooth/ccp_call_control_client/prj.conf b/samples/bluetooth/ccp_call_control_client/prj.conf index e9efbf1fdf702..e55bcbaea1ccd 100644 --- a/samples/bluetooth/ccp_call_control_client/prj.conf +++ b/samples/bluetooth/ccp_call_control_client/prj.conf @@ -17,6 +17,7 @@ CONFIG_BT_TBS_CLIENT_GTBS=y CONFIG_BT_TBS_CLIENT_TBS=y CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES=1 CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME=y +CONFIG_BT_TBS_CLIENT_BEARER_UCI=y CONFIG_UTF8=y # TBS Client may require up to 12 buffers diff --git a/samples/bluetooth/ccp_call_control_client/src/main.c b/samples/bluetooth/ccp_call_control_client/src/main.c index beca758a8600e..bb3c837940f35 100644 --- a/samples/bluetooth/ccp_call_control_client/src/main.c +++ b/samples/bluetooth/ccp_call_control_client/src/main.c @@ -222,6 +222,22 @@ static void ccp_call_control_client_read_bearer_provider_name_cb( } #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void +ccp_call_control_client_read_bearer_uci_cb(struct bt_ccp_call_control_client_bearer *bearer, + int err, const char *name) +{ + if (err != 0) { + LOG_ERR("Failed to read bearer %p UCI: %d\n", (void *)bearer, err); + return; + } + + LOG_INF("Bearer %p UCI: %s", (void *)bearer, name); + + k_sem_give(&sem_ccp_action_completed); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ + static int reset_ccp_call_control_client(void) { int err; @@ -292,24 +308,62 @@ static int read_bearer_name(struct bt_ccp_call_control_client_bearer *bearer) return 0; } -static int read_bearer_names(void) +static int read_bearer_uci(struct bt_ccp_call_control_client_bearer *bearer) { int err; -#if defined(CONFIG_BT_TBS_CLIENT_GTBS) - err = read_bearer_name(client_bearers.gtbs_bearer); + err = bt_ccp_call_control_client_read_bearer_uci(bearer); if (err != 0) { - LOG_ERR("Failed to read name for GTBS bearer: %d", err); return err; } + + err = k_sem_take(&sem_ccp_action_completed, SEM_TIMEOUT); + if (err != 0) { + LOG_ERR("Failed to take sem_ccp_action_completed: %d", err); + return err; + } + + return 0; +} + +static int read_bearer_values(void) +{ + int err; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { + err = read_bearer_name(client_bearers.gtbs_bearer); + if (err != 0) { + LOG_ERR("Failed to read name for GTBS bearer: %d", err); + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_UCI)) { + err = read_bearer_uci(client_bearers.gtbs_bearer); + if (err != 0) { + LOG_ERR("Failed to read UCI for GTBS bearer: %d", err); + return err; + } + } #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ #if defined(CONFIG_BT_TBS_CLIENT_TBS) for (size_t i = 0; i < client_bearers.tbs_count; i++) { - err = read_bearer_name(client_bearers.tbs_bearers[i]); - if (err != 0) { - LOG_ERR("Failed to read name for bearer[%zu]: %d", i, err); - return err; + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { + err = read_bearer_name(client_bearers.tbs_bearers[i]); + if (err != 0) { + LOG_ERR("Failed to read name for bearer[%zu]: %d", i, err); + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_UCI)) { + err = read_bearer_uci(client_bearers.tbs_bearers[i]); + if (err != 0) { + LOG_ERR("Failed to read UCI for bearer[%zu]: %d", i, err); + return err; + } } } #endif /* CONFIG_BT_TBS_CLIENT_TBS */ @@ -322,8 +376,11 @@ static int init_ccp_call_control_client(void) static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { .discover = ccp_call_control_client_discover_cb, #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) - .bearer_provider_name = ccp_call_control_client_read_bearer_provider_name_cb + .bearer_provider_name = ccp_call_control_client_read_bearer_provider_name_cb, #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + .bearer_uci = ccp_call_control_client_read_bearer_uci_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ }; static struct bt_le_scan_cb scan_cbs = { .recv = scan_recv_cb, @@ -384,12 +441,7 @@ int main(void) continue; } - if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { - err = read_bearer_names(); - if (err != 0) { - continue; - } - } + read_bearer_values(); /* Reset if disconnected */ err = k_sem_take(&sem_conn_state_change, K_FOREVER); diff --git a/subsys/bluetooth/audio/ccp_call_control_client.c b/subsys/bluetooth/audio/ccp_call_control_client.c index 9b1e5283cee22..1e91a70ad9add 100644 --- a/subsys/bluetooth/audio/ccp_call_control_client.c +++ b/subsys/bluetooth/audio/ccp_call_control_client.c @@ -289,31 +289,7 @@ int bt_ccp_call_control_client_get_bearers(struct bt_ccp_call_control_client *cl return 0; } -#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) -static void tbs_client_read_bearer_provider_name_cb(struct bt_conn *conn, int err, - uint8_t inst_index, const char *name) -{ - struct bt_ccp_call_control_client *client = get_client_by_conn(conn); - struct bt_ccp_call_control_client_cb *listener, *next; - struct bt_ccp_call_control_client_bearer *bearer; - - atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); - - bearer = get_bearer_by_tbs_index(client, inst_index); - if (bearer == NULL) { - LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", client, - inst_index); - - return; - } - - SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ccp_call_control_client_cbs, listener, next, _node) { - if (listener->bearer_provider_name != NULL) { - listener->bearer_provider_name(bearer, err, name); - } - } -} - +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) || defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) /** * @brief Validates a bearer and provides a client with ownership of the busy flag * @@ -353,6 +329,32 @@ static int validate_bearer_and_get_client(const struct bt_ccp_call_control_clien return 0; } +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI || CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void tbs_client_read_bearer_provider_name_cb(struct bt_conn *conn, int err, + uint8_t inst_index, const char *name) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_cb *listener, *next; + struct bt_ccp_call_control_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + bearer = get_bearer_by_tbs_index(client, inst_index); + if (bearer == NULL) { + LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", client, + inst_index); + + return; + } + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ccp_call_control_client_cbs, listener, next, _node) { + if (listener->bearer_provider_name != NULL) { + listener->bearer_provider_name(bearer, err, name); + } + } +} int bt_ccp_call_control_client_read_bearer_provider_name( struct bt_ccp_call_control_client_bearer *bearer) @@ -389,3 +391,63 @@ int bt_ccp_call_control_client_read_bearer_provider_name( return 0; } #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void tbs_client_read_bearer_uci_cb(struct bt_conn *conn, int err, uint8_t inst_index, + const char *uci) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_cb *listener, *next; + struct bt_ccp_call_control_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + bearer = get_bearer_by_tbs_index(client, inst_index); + if (bearer == NULL) { + LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", client, + inst_index); + + return; + } + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ccp_call_control_client_cbs, listener, next, _node) { + if (listener->bearer_uci != NULL) { + listener->bearer_uci(bearer, err, uci); + } + } +} + +int bt_ccp_call_control_client_read_bearer_uci(struct bt_ccp_call_control_client_bearer *bearer) +{ + struct bt_ccp_call_control_client *client; + int err; + + err = validate_bearer_and_get_client(bearer, &client); + if (err != 0) { + return err; + } + + tbs_client_cbs.bearer_uci = tbs_client_read_bearer_uci_cb; + + err = bt_tbs_client_read_bearer_uci(client->conn, bearer->tbs_index); + if (err != 0) { + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + /* Return expected return values directly */ + if (err == -ENOTCONN || err == -EBUSY) { + LOG_DBG("bt_tbs_client_read_bearer_uci returned %d", err); + + return err; + } + + /* Assert if the return value is -EINVAL as that means we are missing a check */ + __ASSERT(err != -EINVAL, "err shall not be -EINVAL"); + + LOG_DBG("Unexpected error from bt_tbs_client_read_bearer_uci: %d", err); + + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ diff --git a/subsys/bluetooth/audio/shell/ccp_call_control_client.c b/subsys/bluetooth/audio/shell/ccp_call_control_client.c index 1152d62fe8fa5..156f9e5c6a302 100644 --- a/subsys/bluetooth/audio/shell/ccp_call_control_client.c +++ b/subsys/bluetooth/audio/shell/ccp_call_control_client.c @@ -65,11 +65,27 @@ ccp_call_control_client_bearer_provider_name_cb(struct bt_ccp_call_control_clien } #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void ccp_call_control_client_bearer_uci_cb(struct bt_ccp_call_control_client_bearer *bearer, + int err, const char *name) +{ + if (err != 0) { + bt_shell_error("Failed to read bearer %p UCI: %d", (void *)bearer, err); + return; + } + + bt_shell_info("Bearer %p UCI: %s", (void *)bearer, name); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ + static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { .discover = ccp_call_control_client_discover_cb, #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) .bearer_provider_name = ccp_call_control_client_bearer_provider_name_cb, #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + .bearer_uci = ccp_call_control_client_bearer_uci_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ }; static int cmd_ccp_call_control_client_discover(const struct shell *sh, size_t argc, char *argv[]) @@ -187,6 +203,37 @@ static int cmd_ccp_call_control_client_read_bearer_name(const struct shell *sh, return 0; } +static int cmd_ccp_call_control_client_read_bearer_uci(const struct shell *sh, size_t argc, + char *argv[]) +{ + struct bt_ccp_call_control_client_bearer *bearer; + unsigned long index = 0; + int err; + + if (argc > 1) { + index = validate_and_get_index(sh, argv[1]); + if (index < 0) { + return index; + } + } + + bearer = get_bearer_by_index(index); + if (bearer == NULL) { + shell_error(sh, "Failed to get bearer for index %lu", index); + + return -ENOEXEC; + } + + err = bt_ccp_call_control_client_read_bearer_uci(bearer); + if (err != 0) { + shell_error(sh, "Failed to read bearer[%lu] UCI: %d", index, err); + + return -ENOEXEC; + } + + return 0; +} + static int cmd_ccp_call_control_client(const struct shell *sh, size_t argc, char **argv) { if (argc > 1) { @@ -204,6 +251,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE(ccp_call_control_client_cmds, cmd_ccp_call_control_client_discover, 1, 0), SHELL_CMD_ARG(read_bearer_name, NULL, "Get bearer name [index]", cmd_ccp_call_control_client_read_bearer_name, 1, 1), + SHELL_CMD_ARG(read_bearer_name, NULL, "Get bearer UCI [index]", + cmd_ccp_call_control_client_read_bearer_uci, 1, 1), SHELL_SUBCMD_SET_END); SHELL_CMD_ARG_REGISTER(ccp_call_control_client, &ccp_call_control_client_cmds, diff --git a/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h b/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h index 55feddbbeaf35..9ec22d90cd0f3 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h +++ b/tests/bluetooth/audio/ccp_call_control_client/include/ccp_call_control_client.h @@ -21,5 +21,7 @@ DECLARE_FAKE_VOID_FUNC(mock_ccp_call_control_client_discover_cb, struct bt_ccp_call_control_client_bearers *); DECLARE_FAKE_VOID_FUNC(mock_ccp_call_control_client_bearer_provider_name_cb, struct bt_ccp_call_control_client_bearer *, int, const char *); +DECLARE_FAKE_VOID_FUNC(mock_ccp_call_control_client_bearer_uci_cb, + struct bt_ccp_call_control_client_bearer *, int, const char *); #endif /* MOCKS_CCP_CALL_CONTROL_CLIENT_H_ */ diff --git a/tests/bluetooth/audio/ccp_call_control_client/prj.conf b/tests/bluetooth/audio/ccp_call_control_client/prj.conf index 5f0f8a3f2a9f7..f0c878d552359 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/prj.conf +++ b/tests/bluetooth/audio/ccp_call_control_client/prj.conf @@ -11,6 +11,8 @@ CONFIG_BT_CCP_CALL_CONTROL_CLIENT=y CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT=2 CONFIG_BT_TBS_CLIENT_TBS=y CONFIG_BT_TBS_CLIENT_GTBS=y +CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME=y +CONFIG_BT_TBS_CLIENT_BEARER_UCI=y CONFIG_UTF8=y CONFIG_ASSERT=y diff --git a/tests/bluetooth/audio/ccp_call_control_client/src/test_procedures.c b/tests/bluetooth/audio/ccp_call_control_client/src/test_procedures.c index 98564f984aceb..ac1628ab84255 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/src/test_procedures.c +++ b/tests/bluetooth/audio/ccp_call_control_client/src/test_procedures.c @@ -163,3 +163,53 @@ static ZTEST_F(ccp_call_control_client_procedures_test_suite, err = bt_ccp_call_control_client_read_bearer_provider_name(invalid_bearer); zassert_equal(err, -EEXIST, "Unexpected return value %d", err); } + +static ZTEST_F(ccp_call_control_client_procedures_test_suite, + test_ccp_call_control_client_read_bearer_uci) +{ + int err; + + err = bt_ccp_call_control_client_read_bearer_uci(fixture->bearers[0]); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_ccp_call_control_client_cb.bearer_uci", 1, + mock_ccp_call_control_client_bearer_uci_cb_fake.call_count); + zassert_not_null( + mock_ccp_call_control_client_bearer_uci_cb_fake.arg0_history[0]); /* bearer */ + zassert_equal(0, mock_ccp_call_control_client_bearer_uci_cb_fake.arg1_history[0]); /* err */ + zassert_not_null(mock_ccp_call_control_client_bearer_uci_cb_fake.arg2_history[0]); /* uci */ +} + +static ZTEST_F(ccp_call_control_client_procedures_test_suite, + test_ccp_call_control_client_read_bearer_uci_inval_null_bearer) +{ + int err; + + err = bt_ccp_call_control_client_read_bearer_uci(NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_procedures_test_suite, + test_ccp_call_control_client_read_bearer_uci_inval_not_discovered) +{ + int err; + + /* Fake disconnection to clear the discovered value for the bearers*/ + mock_bt_conn_disconnected(&fixture->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + /* Mark as connected again but without discovering */ + test_conn_init(&fixture->conn); + + err = bt_ccp_call_control_client_read_bearer_uci(fixture->bearers[0]); + zassert_equal(err, -EFAULT, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_client_procedures_test_suite, + test_ccp_call_control_client_read_bearer_uci_inval_bearer) +{ + struct bt_ccp_call_control_client_bearer *invalid_bearer = + (struct bt_ccp_call_control_client_bearer *)0xdeadbeef; + int err; + + err = bt_ccp_call_control_client_read_bearer_uci(invalid_bearer); + zassert_equal(err, -EEXIST, "Unexpected return value %d", err); +} diff --git a/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c b/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c index 8381464c1806c..5f61fbd2e658e 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c +++ b/tests/bluetooth/audio/ccp_call_control_client/uut/ccp_call_control_client.c @@ -19,12 +19,19 @@ DEFINE_FAKE_VOID_FUNC(mock_ccp_call_control_client_discover_cb, struct bt_ccp_ca DEFINE_FAKE_VOID_FUNC(mock_ccp_call_control_client_bearer_provider_name_cb, struct bt_ccp_call_control_client_bearer *, int, const char *); #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +DEFINE_FAKE_VOID_FUNC(mock_ccp_call_control_client_bearer_uci_cb, + struct bt_ccp_call_control_client_bearer *, int, const char *); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ struct bt_ccp_call_control_client_cb mock_ccp_call_control_client_cb = { .discover = mock_ccp_call_control_client_discover_cb, #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) .bearer_provider_name = mock_ccp_call_control_client_bearer_provider_name_cb, #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + .bearer_uci = mock_ccp_call_control_client_bearer_uci_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ }; void mock_ccp_call_control_client_init(void) diff --git a/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c b/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c index 9eb86f878373e..0df53d9a2a0de 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c +++ b/tests/bluetooth/audio/ccp_call_control_client/uut/tbs_client.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ int bt_tbs_client_discover(struct bt_conn *conn) return 0; } +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) int bt_tbs_client_read_bearer_provider_name(struct bt_conn *conn, uint8_t inst_index) { if (conn == NULL) { @@ -46,3 +48,19 @@ int bt_tbs_client_read_bearer_provider_name(struct bt_conn *conn, uint8_t inst_i return 0; } +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +int bt_tbs_client_read_bearer_uci(struct bt_conn *conn, uint8_t inst_index) +{ + if (conn == NULL) { + return -ENOTCONN; + } + + if (tbs_cbs != NULL && tbs_cbs->bearer_provider_name != NULL) { + tbs_cbs->bearer_uci(conn, 0, inst_index, "bearer UCI"); + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ diff --git a/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c b/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c index 5631b8cb0ec83..81fc8e2dc11c5 100644 --- a/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c +++ b/tests/bsim/bluetooth/audio/src/ccp_call_control_client_test.c @@ -24,6 +24,7 @@ extern enum bst_result_t bst_result; CREATE_FLAG(flag_discovery_complete); CREATE_FLAG(flag_bearer_name_read); +CREATE_FLAG(flag_bearer_uci); static struct bt_ccp_call_control_client *call_control_client; static struct bt_ccp_call_control_client_bearers client_bearers; @@ -63,6 +64,21 @@ static void ccp_call_control_client_read_bearer_provider_name_cb( SET_FLAG(flag_bearer_name_read); } #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void +ccp_call_control_client_read_bearer_uci_cb(struct bt_ccp_call_control_client_bearer *bearer, + int err, const char *uci) +{ + if (err != 0) { + FAIL("Failed to read bearer %p provider UCI: %d\n", (void *)bearer, err); + return; + } + + LOG_INF("Bearer %p provider UCI: %s", (void *)bearer, uci); + + SET_FLAG(flag_bearer_uci); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ static void discover_tbs(void) { @@ -94,17 +110,46 @@ static void read_bearer_name(struct bt_ccp_call_control_client_bearer *bearer) WAIT_FOR_FLAG(flag_bearer_name_read); } -static void read_bearer_names(void) +static void read_bearer_uci(struct bt_ccp_call_control_client_bearer *bearer) +{ + int err; + + UNSET_FLAG(flag_bearer_uci); + + err = bt_ccp_call_control_client_read_bearer_uci(bearer); + if (err != 0) { + FAIL("Failed to read UCI of bearer %p: %d", bearer, err); + return; + } + + WAIT_FOR_FLAG(flag_bearer_uci); +} + +static int read_bearer_values(void) { #if defined(CONFIG_BT_TBS_CLIENT_GTBS) - read_bearer_name(client_bearers.gtbs_bearer); + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { + read_bearer_name(client_bearers.gtbs_bearer); + } + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_UCI)) { + read_bearer_uci(client_bearers.gtbs_bearer); + } #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ #if defined(CONFIG_BT_TBS_CLIENT_TBS) for (size_t i = 0; i < client_bearers.tbs_count; i++) { - read_bearer_name(client_bearers.tbs_bearers[i]); + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { + read_bearer_name(client_bearers.tbs_bearers[i]); + } + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_UCI)) { + read_bearer_uci(client_bearers.tbs_bearers[i]); + } } #endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + return 0; } static void init(void) @@ -112,8 +157,11 @@ static void init(void) static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { .discover = ccp_call_control_client_discover_cb, #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) - .bearer_provider_name = ccp_call_control_client_read_bearer_provider_name_cb + .bearer_provider_name = ccp_call_control_client_read_bearer_provider_name_cb, #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + .bearer_uci = ccp_call_control_client_read_bearer_uci_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ }; int err; @@ -146,9 +194,7 @@ static void test_main(void) discover_tbs(); discover_tbs(); /* test that we can discover twice */ - if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { - read_bearer_names(); - } + read_bearer_values(); err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); if (err != 0) {