From a20843a1d164c1ad5eca2531be764e5b109e8bcd Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Wed, 2 Oct 2024 10:12:19 +0200 Subject: [PATCH 1/2] Bluetooth: CCP: Add support for set/get provider name Add support for setting and getting the bearer provider name. For now the name will be duplicated by the TBS implementation, but will be optimizied in the future so only one copy of the name exists. Signed-off-by: Emil Gydesen --- .../bluetooth/shell/audio/ccp.rst | 27 +++- include/zephyr/bluetooth/audio/ccp.h | 27 ++++ subsys/bluetooth/audio/Kconfig.ccp | 7 + subsys/bluetooth/audio/Kconfig.tbs | 2 +- .../bluetooth/audio/ccp_call_control_server.c | 69 ++++++++ .../audio/shell/ccp_call_control_server.c | 81 ++++++++++ subsys/bluetooth/audio/tbs.c | 4 + .../audio/ccp_call_control_server/src/main.c | 149 +++++++++++++++++- 8 files changed, 363 insertions(+), 3 deletions(-) diff --git a/doc/connectivity/bluetooth/shell/audio/ccp.rst b/doc/connectivity/bluetooth/shell/audio/ccp.rst index 0fbf3ed2181d..8aab2829ded4 100644 --- a/doc/connectivity/bluetooth/shell/audio/ccp.rst +++ b/doc/connectivity/bluetooth/shell/audio/ccp.rst @@ -13,12 +13,18 @@ The Server can be controlled locally, or by a remote device (when in a call). Fo example a remote device may initiate a call to the server, or the Server may initiate a call to remote device, without a client. +For all commands that take an optional :code:`index`, if the index is not supplied then it defaults +to :code:`0` which is the GTBS bearer. + .. code-block:: console ccp_call_control_server --help ccp_call_control_server - Bluetooth CCP Call Control Server shell commands Subcommands: - init : Initialize CCP Call Control Server + init : Initialize CCP Call Control Server + set_bearer_name : Set bearer name [index] + get_bearer_name : Get bearer name [index] + Example Usage ============= @@ -34,6 +40,25 @@ Setup Registered bearer[1] uart:~$ bt connect xx:xx:xx:xx:xx:xx public +Setting and getting the bearer name +----------------------------------- + +.. code-block:: console + + uart:~$ ccp_call_control_server get_bearer_name + Bearer[0] name: Generic TBS + uart:~$ ccp_call_control_server set_bearer_name "New name" + Bearer[0] name: New name + uart:~$ ccp_call_control_server get_bearer_name + Bearer[0] name: New name + uart:~$ ccp_call_control_server get_bearer_name 1 + Bearer[1] name: Telephone Bearer #1 + uart:~$ ccp_call_control_server set_bearer_name 1 "New TBS name" + Bearer[1] name: New TBS name + uart:~$ ccp_call_control_server get_bearer_name 1 + Bearer[1] name: New TBS name + + Call Control Client ******************* The Call Control Client is a role that typically resides on resource constrained devices such as diff --git a/include/zephyr/bluetooth/audio/ccp.h b/include/zephyr/bluetooth/audio/ccp.h index 5c907d569590..c57432c9cf6b 100644 --- a/include/zephyr/bluetooth/audio/ccp.h +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -93,6 +93,33 @@ int bt_ccp_call_control_server_register_bearer(const struct bt_tbs_register_para */ int bt_ccp_call_control_server_unregister_bearer(struct bt_ccp_call_control_server_bearer *bearer); +/** + * @brief Set a new bearer provider name. + * + * @param bearer The bearer to set the name for. + * @param name The new bearer provider name. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p name is NULL, or @p name is the empty string or @p name is larger + * than @kconfig{CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH} + * @retval -EFAULT @p bearer is not registered + */ +int bt_ccp_call_control_server_set_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] name Pointer that will be updated to be the bearer provider name. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p name is NULL + * @retval -EFAULT @p bearer is not registered + */ +int bt_ccp_call_control_server_get_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, const char **name); + /** @} */ /* End of group bt_ccp_call_control_server */ /** diff --git a/subsys/bluetooth/audio/Kconfig.ccp b/subsys/bluetooth/audio/Kconfig.ccp index c397b831cfc0..95d2aac65e72 100644 --- a/subsys/bluetooth/audio/Kconfig.ccp +++ b/subsys/bluetooth/audio/Kconfig.ccp @@ -50,6 +50,13 @@ config BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT help The number of supported telephone bearers on the CCP Call Control Server +config BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH + int "The maximum length of the bearer provider name excluding null terminator" + default BT_TBS_MAX_PROVIDER_NAME_LENGTH + range 1 BT_TBS_MAX_PROVIDER_NAME_LENGTH + help + Sets the maximum length of the bearer provider name. + module = BT_CCP_CALL_CONTROL_SERVER module-str = "Call Control Profile Call Control Server" source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/bluetooth/audio/Kconfig.tbs b/subsys/bluetooth/audio/Kconfig.tbs index 557786a833cd..3a06931495b0 100644 --- a/subsys/bluetooth/audio/Kconfig.tbs +++ b/subsys/bluetooth/audio/Kconfig.tbs @@ -263,7 +263,7 @@ config BT_TBS_MAX_URI_LENGTH config BT_TBS_MAX_PROVIDER_NAME_LENGTH int "The maximum length of the bearer provider name" default 30 - range 0 512 + range 1 512 help Sets the maximum length of the bearer provider name. diff --git a/subsys/bluetooth/audio/ccp_call_control_server.c b/subsys/bluetooth/audio/ccp_call_control_server.c index c1f955743ba8..fc14bb178f1a 100644 --- a/subsys/bluetooth/audio/ccp_call_control_server.c +++ b/subsys/bluetooth/audio/ccp_call_control_server.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,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]; uint8_t tbs_index; bool registered; }; @@ -70,6 +72,8 @@ int bt_ccp_call_control_server_register_bearer(const struct bt_tbs_register_para free_bearer->registered = true; free_bearer->tbs_index = (uint8_t)ret; + (void)utf8_lcpy(free_bearer->provider_name, param->provider_name, + sizeof(free_bearer->provider_name)); *bearer = free_bearer; return 0; @@ -105,3 +109,68 @@ int bt_ccp_call_control_server_unregister_bearer(struct bt_ccp_call_control_serv return 0; } + +int bt_ccp_call_control_server_set_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, const char *name) +{ + size_t len; + + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + CHECKIF(name == NULL) { + LOG_DBG("name is NULL"); + + return -EINVAL; + } + + if (!bearer->registered) { + LOG_DBG("Bearer %p not registered", bearer); + + return -EFAULT; + } + + len = strlen(name); + if (len > CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH || len == 0) { + LOG_DBG("Invalid name length: %zu", len); + + return -EINVAL; + } + + if (strcmp(bearer->provider_name, name) == 0) { + return 0; + } + + (void)utf8_lcpy(bearer->provider_name, name, sizeof(bearer->provider_name)); + + return bt_tbs_set_bearer_provider_name(bearer->tbs_index, name); +} + +int bt_ccp_call_control_server_get_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, const char **name) +{ + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + CHECKIF(name == NULL) { + LOG_DBG("name is NULL"); + + return -EINVAL; + } + + if (!bearer->registered) { + LOG_DBG("Bearer %p not registered", bearer); + + return -EFAULT; + } + + *name = bearer->provider_name; + + 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 dc0bcc8bf8c0..4934fe8b52c3 100644 --- a/subsys/bluetooth/audio/shell/ccp_call_control_server.c +++ b/subsys/bluetooth/audio/shell/ccp_call_control_server.c @@ -15,6 +15,7 @@ #include #include #include +#include static struct bt_ccp_call_control_server_bearer *bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]; @@ -79,6 +80,81 @@ static int cmd_ccp_call_control_server_init(const struct shell *sh, size_t argc, return 0; } +static int validate_and_get_index(const struct shell *sh, const char *index_arg) +{ + unsigned long index; + int err = 0; + + index = shell_strtoul(index_arg, 0, &err); + if (err != 0) { + shell_error(sh, "Could not parse index: %d", err); + + return -ENOEXEC; + } + + if (index >= CONFIG_BT_TBS_BEARER_COUNT) { + shell_error(sh, "Invalid index: %lu", index); + + return -ENOEXEC; + } + + return (int)index; +} + +static int cmd_ccp_call_control_server_set_bearer_name(const struct shell *sh, size_t argc, + char *argv[]) +{ + const char *name; + int index = 0; + int err = 0; + + if (argc > 2) { + index = validate_and_get_index(sh, argv[1]); + if (index < 0) { + return index; + } + } + + name = argv[argc - 1]; + + err = bt_ccp_call_control_server_set_bearer_provider_name(bearers[index], name); + if (err != 0) { + shell_error(sh, "Failed to set bearer[%d] name: %d", index, err); + + return -ENOEXEC; + } + + shell_print(sh, "Bearer[%d] name: %s", index, name); + + return 0; +} + +static int cmd_ccp_call_control_server_get_bearer_name(const struct shell *sh, size_t argc, + char *argv[]) +{ + const char *name; + int index = 0; + 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_provider_name(bearers[index], &name); + if (err != 0) { + shell_error(sh, "Failed to get bearer[%d] name: %d", index, err); + + return -ENOEXEC; + } + + shell_print(sh, "Bearer[%d] name: %s", index, name); + + return 0; +} + static int cmd_ccp_call_control_server(const struct shell *sh, size_t argc, char **argv) { if (argc > 1) { @@ -93,6 +169,11 @@ static int cmd_ccp_call_control_server(const struct shell *sh, size_t argc, char SHELL_STATIC_SUBCMD_SET_CREATE(ccp_call_control_server_cmds, SHELL_CMD_ARG(init, NULL, "Initialize CCP Call Control Server", cmd_ccp_call_control_server_init, 1, 0), + SHELL_CMD_ARG(set_bearer_name, NULL, + "Set bearer name [index] ", + 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_SUBCMD_SET_END); SHELL_CMD_ARG_REGISTER(ccp_call_control_server, &ccp_call_control_server_cmds, diff --git a/subsys/bluetooth/audio/tbs.c b/subsys/bluetooth/audio/tbs.c index 467eb4b9768e..3e99d27fd56f 100644 --- a/subsys/bluetooth/audio/tbs.c +++ b/subsys/bluetooth/audio/tbs.c @@ -66,6 +66,10 @@ struct tbs_flags { /* A service instance can either be a GTBS or a TBS instance */ struct tbs_inst { /* Attribute values */ + /* TODO: The provider name should be removed from the tbs_inst and instead by stored by the + * user of TBS. This will be done once the CCP API is complete as the CCP Server will own + * all the data instead of the TBS + */ char provider_name[CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH]; char uci[BT_TBS_MAX_UCI_SIZE]; uint8_t technology; 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 7ed6c1ef0e19..af09be763bb1 100644 --- a/tests/bluetooth/audio/ccp_call_control_server/src/main.c +++ b/tests/bluetooth/audio/ccp_call_control_server/src/main.c @@ -26,6 +26,8 @@ DEFINE_FFF_GLOBALS; +#define DEFAULT_BEARER_NAME "test" + struct ccp_call_control_server_test_suite_fixture { /** Need 1 additional bearer than the max to trigger some corner cases */ struct bt_ccp_call_control_server_bearer @@ -81,7 +83,7 @@ ZTEST_SUITE(ccp_call_control_server_test_suite, NULL, ccp_call_control_server_te static void register_default_bearer(struct ccp_call_control_server_test_suite_fixture *fixture) { const struct bt_tbs_register_param register_param = { - .provider_name = "test", + .provider_name = DEFAULT_BEARER_NAME, .uci = "un999", .uri_schemes_supported = "tel", .gtbs = true, @@ -262,3 +264,148 @@ static ZTEST_F(ccp_call_control_server_test_suite, err = bt_ccp_call_control_server_unregister_bearer(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_set_bearer_provider_name) +{ + const char *new_bearer_name = "New bearer name"; + const char *res_bearer_name; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_set_bearer_provider_name(fixture->bearers[0], + new_bearer_name); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_ccp_call_control_server_get_bearer_provider_name(fixture->bearers[0], + &res_bearer_name); + zassert_equal(err, 0, "Unexpected return value %d", err); + + zassert_str_equal(new_bearer_name, res_bearer_name, "%s != %s", new_bearer_name, + res_bearer_name); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_bt_ccp_call_control_server_set_bearer_provider_name_inval_not_registered) +{ + const char *new_bearer_name = "New 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_set_bearer_provider_name(fixture->bearers[0], + new_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_set_bearer_provider_name_inval_null_bearer) +{ + const char *new_bearer_name = "New bearer name"; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_set_bearer_provider_name(NULL, new_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_set_bearer_provider_name_inval_null_name) +{ + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_set_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_set_bearer_provider_name_inval_empty_name) +{ + const char *inval_bearer_name = ""; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_set_bearer_provider_name(fixture->bearers[0], + inval_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_set_bearer_provider_name_inval_long_name) +{ + char inval_bearer_name[CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH + 1]; + int err; + + for (size_t i = 0; i < ARRAY_SIZE(inval_bearer_name); i++) { + inval_bearer_name[i] = 'a'; + } + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_set_bearer_provider_name(fixture->bearers[0], + inval_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_provider_name) +{ + const char *res_bearer_name; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_get_bearer_provider_name(fixture->bearers[0], + &res_bearer_name); + zassert_equal(err, 0, "Unexpected return value %d", err); + + zassert_str_equal(DEFAULT_BEARER_NAME, res_bearer_name, "%s != %s", DEFAULT_BEARER_NAME, + res_bearer_name); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_bt_ccp_call_control_server_get_bearer_provider_name_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_provider_name(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_provider_name_inval_null_bearer) +{ + const char *res_bearer_name; + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_get_bearer_provider_name(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_provider_name_inval_null_name) +{ + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_get_bearer_provider_name(fixture->bearers[0], NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} From 2188861a09f51e55276fbcd431e4cba33049af2c Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Wed, 2 Oct 2024 10:12:19 +0200 Subject: [PATCH 2/2] Bluetooth: CCP: Client: Add support for get provider name Add support for getting the remote bearer provider name. Signed-off-by: Emil Gydesen --- .../bluetooth/shell/audio/ccp.rst | 7 + 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 | 130 ++++++++++++++ .../audio/shell/ccp_call_control_client.c | 122 ++++++++++++- subsys/bluetooth/audio/tbs_internal.h | 5 + .../ccp_call_control_client/CMakeLists.txt | 2 + .../include/ccp_call_control_client.h | 2 + .../include/test_common.h | 17 ++ .../audio/ccp_call_control_client/src/main.c | 18 +- .../ccp_call_control_client/src/test_common.c | 37 ++++ .../src/test_procedures.c | 165 ++++++++++++++++++ .../uut/ccp_call_control_client.c | 7 + .../ccp_call_control_client/uut/tbs_client.c | 14 ++ .../audio/src/ccp_call_control_client_test.c | 59 ++++++- .../ccp/call_control_client/src/test_main.c | 6 +- 17 files changed, 673 insertions(+), 33 deletions(-) create mode 100644 tests/bluetooth/audio/ccp_call_control_client/include/test_common.h create mode 100644 tests/bluetooth/audio/ccp_call_control_client/src/test_common.c create mode 100644 tests/bluetooth/audio/ccp_call_control_client/src/test_procedures.c diff --git a/doc/connectivity/bluetooth/shell/audio/ccp.rst b/doc/connectivity/bluetooth/shell/audio/ccp.rst index 8aab2829ded4..e5f81db914ad 100644 --- a/doc/connectivity/bluetooth/shell/audio/ccp.rst +++ b/doc/connectivity/bluetooth/shell/audio/ccp.rst @@ -83,3 +83,10 @@ Example Usage when connected uart:~$ ccp_call_control_client discover Discovery completed with GTBS and 1 TBS bearers + +.. code-block:: console + + uart:~$ ccp_call_control_client read_bearer_name + Bearer 0x20046254 name: Generic TBS + uart:~$ ccp_call_control_client read_bearer_name 1 + Bearer 0x20046256 name: Telephone Bearer #1 diff --git a/include/zephyr/bluetooth/audio/ccp.h b/include/zephyr/bluetooth/audio/ccp.h index c57432c9cf6b..0dd68befe296 100644 --- a/include/zephyr/bluetooth/audio/ccp.h +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -169,6 +169,21 @@ struct bt_ccp_call_control_client_cb { void (*discover)(struct bt_ccp_call_control_client *client, int err, struct bt_ccp_call_control_client_bearers *bearers); +#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 name The bearer provider name. NULL if @p err is not 0. + */ + void (*bearer_provider_name)(struct bt_ccp_call_control_client_bearer *bearer, int err, + const char *name); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + /** @cond INTERNAL_HIDDEN */ /** Internally used field for list handling */ sys_snode_t _node; @@ -230,6 +245,23 @@ int bt_ccp_call_control_client_unregister_cb(struct bt_ccp_call_control_client_c int bt_ccp_call_control_client_get_bearers(struct bt_ccp_call_control_client *client, struct bt_ccp_call_control_client_bearers *bearers); +/** + * @brief Read the bearer provider name of a remote TBS bearer. + * + * @kconfig_dep{CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME} + * + * @param bearer The bearer to read the name 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_provider_name( + 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 85549f2c321c..e9efbf1fdf70 100644 --- a/samples/bluetooth/ccp_call_control_client/prj.conf +++ b/samples/bluetooth/ccp_call_control_client/prj.conf @@ -16,6 +16,7 @@ CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT=2 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_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 db8341204a47..beca758a8600 100644 --- a/samples/bluetooth/ccp_call_control_client/src/main.c +++ b/samples/bluetooth/ccp_call_control_client/src/main.c @@ -30,8 +30,8 @@ LOG_MODULE_REGISTER(ccp_call_control_client, CONFIG_LOG_DEFAULT_LEVEL); #define SEM_TIMEOUT K_SECONDS(10) static struct bt_conn *peer_conn; -/* client is not static as it is used for testing purposes */ -struct bt_ccp_call_control_client *client; +/* call_control_client is not static as it is used for testing purposes */ +struct bt_ccp_call_control_client *call_control_client; static struct bt_ccp_call_control_client_bearers client_bearers; static K_SEM_DEFINE(sem_conn_state_change, 0, 1); @@ -61,7 +61,7 @@ static void disconnected_cb(struct bt_conn *conn, uint8_t reason) bt_conn_unref(peer_conn); peer_conn = NULL; - client = NULL; + call_control_client = NULL; memset(&client_bearers, 0, sizeof(client_bearers)); k_sem_give(&sem_conn_state_change); } @@ -207,6 +207,21 @@ static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_clien k_sem_give(&sem_ccp_action_completed); } +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void ccp_call_control_client_read_bearer_provider_name_cb( + struct bt_ccp_call_control_client_bearer *bearer, int err, const char *name) +{ + if (err != 0) { + LOG_ERR("Failed to read bearer %p provider name: %d\n", (void *)bearer, err); + return; + } + + LOG_INF("Bearer %p provider name: %s", (void *)bearer, name); + + k_sem_give(&sem_ccp_action_completed); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + static int reset_ccp_call_control_client(void) { int err; @@ -244,7 +259,7 @@ static int discover_services(void) LOG_INF("Discovering GTBS and TBS"); - err = bt_ccp_call_control_client_discover(peer_conn, &client); + err = bt_ccp_call_control_client_discover(peer_conn, &call_control_client); if (err != 0) { LOG_ERR("Failed to discover: %d", err); return err; @@ -259,13 +274,59 @@ static int discover_services(void) return 0; } +static int read_bearer_name(struct bt_ccp_call_control_client_bearer *bearer) +{ + int err; + + err = bt_ccp_call_control_client_read_bearer_provider_name(bearer); + if (err != 0) { + 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_names(void) +{ + int err; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + err = read_bearer_name(client_bearers.gtbs_bearer); + if (err != 0) { + LOG_ERR("Failed to read name 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; + } + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + return 0; +} + static int init_ccp_call_control_client(void) { - static struct bt_le_scan_cb scan_cbs = { - .recv = scan_recv_cb, - }; 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 +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + }; + static struct bt_le_scan_cb scan_cbs = { + .recv = scan_recv_cb, }; int err; @@ -323,6 +384,13 @@ int main(void) continue; } + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME)) { + err = read_bearer_names(); + if (err != 0) { + continue; + } + } + /* Reset if disconnected */ err = k_sem_take(&sem_conn_state_change, K_FOREVER); if (err != 0) { diff --git a/subsys/bluetooth/audio/ccp_call_control_client.c b/subsys/bluetooth/audio/ccp_call_control_client.c index 06935ea2e476..90b3cc7877f6 100644 --- a/subsys/bluetooth/audio/ccp_call_control_client.c +++ b/subsys/bluetooth/audio/ccp_call_control_client.c @@ -28,6 +28,7 @@ LOG_MODULE_REGISTER(bt_ccp_call_control_client, CONFIG_BT_CCP_CALL_CONTROL_CLIEN static sys_slist_t ccp_call_control_client_cbs = SYS_SLIST_STATIC_INIT(&ccp_call_control_client_cbs); +static struct bt_tbs_client_cb tbs_client_cbs; static struct bt_tbs_client_cb tbs_client_cbs; @@ -53,6 +54,32 @@ struct bt_ccp_call_control_client { static struct bt_ccp_call_control_client clients[CONFIG_BT_MAX_CONN]; +static struct bt_ccp_call_control_client_bearer * +get_bearer_by_tbs_index(struct bt_ccp_call_control_client *client, uint8_t index) +{ + for (size_t i = 0U; i < ARRAY_SIZE(client->bearers); i++) { + struct bt_ccp_call_control_client_bearer *bearer = &client->bearers[i]; + + if (bearer->discovered && bearer->tbs_index == index) { + return bearer; + } + } + + return NULL; +} + +static struct bt_ccp_call_control_client * +get_client_by_bearer(const struct bt_ccp_call_control_client_bearer *bearer) +{ + for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) { + if (IS_ARRAY_ELEMENT(clients[i].bearers, bearer)) { + return &clients[i]; + } + } + + return NULL; +} + static struct bt_ccp_call_control_client *get_client_by_conn(const struct bt_conn *conn) { return &clients[bt_conn_index(conn)]; @@ -84,6 +111,8 @@ static void disconnected_cb(struct bt_conn *conn, uint8_t reason) if (client->conn == conn) { bt_conn_unref(client->conn); client->conn = NULL; + + memset(client->bearers, 0, sizeof(client->bearers)); } } @@ -252,3 +281,104 @@ 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); + } + } +} + +/** + * @brief Validates a bearer and provides a client with ownership of the busy flag + * + * @param[in] bearer The bearer to validate + * @param[out] client A client identified by the @p bearer with the busy flag set if return + * value is 0. + * + * @return 0 if the bearer is valid and the @p client has been populated, else an error. + */ +static int validate_bearer_and_get_client(const struct bt_ccp_call_control_client_bearer *bearer, + struct bt_ccp_call_control_client **client) +{ + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + *client = get_client_by_bearer(bearer); + if (*client == NULL) { + LOG_DBG("Could not identify client from bearer %p", bearer); + + return -EEXIST; + } + + if (!bearer->discovered) { + LOG_DBG("bearer %p is not discovered", bearer); + + return -EFAULT; + } + + if (atomic_test_and_set_bit((*client)->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY)) { + LOG_DBG("Client %p identified by bearer %p is busy", *client, bearer); + + return -EBUSY; + } + + return 0; +} + +int bt_ccp_call_control_client_read_bearer_provider_name( + 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_provider_name = tbs_client_read_bearer_provider_name_cb; + + err = bt_tbs_client_read_bearer_provider_name(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_provider_name 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_provider_name: %d", err); + + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ diff --git a/subsys/bluetooth/audio/shell/ccp_call_control_client.c b/subsys/bluetooth/audio/shell/ccp_call_control_client.c index 8f1b210d458e..1152d62fe8fa 100644 --- a/subsys/bluetooth/audio/shell/ccp_call_control_client.c +++ b/subsys/bluetooth/audio/shell/ccp_call_control_client.c @@ -17,12 +17,19 @@ #include #include #include +#include +#include #include "common/bt_shell_private.h" #include "host/shell/bt.h" static struct bt_ccp_call_control_client *clients[CONFIG_BT_MAX_CONN]; +static struct bt_ccp_call_control_client *get_client_by_conn(const struct bt_conn *conn) +{ + return clients[bt_conn_index(conn)]; +} + static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client *client, int err, struct bt_ccp_call_control_client_bearers *bearers) { @@ -40,14 +47,34 @@ static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_clien tbs_count = bearers->tbs_count; #endif /* CONFIG_BT_TBS_CLIENT_TBS */ - bt_shell_info("Discovery completed with %s%u TBS bearers", + bt_shell_info("Discovery completed for client %p with %s%u TBS bearers", client, gtbs_bearer != NULL ? "GTBS and " : "", tbs_count); } +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void +ccp_call_control_client_bearer_provider_name_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 name: %d", (void *)bearer, err); + return; + } + + bt_shell_info("Bearer %p name: %s", (void *)bearer, name); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +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 */ +}; + static int cmd_ccp_call_control_client_discover(const struct shell *sh, size_t argc, char *argv[]) { static bool cb_registered; - int err; if (default_conn == NULL) { @@ -56,16 +83,14 @@ static int cmd_ccp_call_control_client_discover(const struct shell *sh, size_t a } if (!cb_registered) { - static struct bt_ccp_call_control_client_cb ccp_call_control_client_cbs = { - .discover = ccp_call_control_client_discover_cb, - }; - err = bt_ccp_call_control_client_register_cb(&ccp_call_control_client_cbs); if (err != 0) { shell_error(sh, "Failed to register CCP Call Control Client cbs (err %d)", err); return -ENOEXEC; } + + cb_registered = true; } err = bt_ccp_call_control_client_discover(default_conn, @@ -79,6 +104,89 @@ static int cmd_ccp_call_control_client_discover(const struct shell *sh, size_t a return 0; } +static int validate_and_get_index(const struct shell *sh, const char *index_arg) +{ + unsigned long index; + int err = 0; + + index = shell_strtoul(index_arg, 0, &err); + if (err != 0) { + shell_error(sh, "Could not parse index: %d", err); + + return -ENOEXEC; + } + + if (index >= CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT) { + shell_error(sh, "Invalid index: %lu", index); + + return -ENOEXEC; + } + + return (int)index; +} + +static struct bt_ccp_call_control_client_bearer *get_bearer_by_index(uint8_t index) +{ + struct bt_ccp_call_control_client_bearers bearers; + struct bt_ccp_call_control_client *client; + int err; + + client = get_client_by_conn(default_conn); + if (client == NULL) { + return NULL; + } + + err = bt_ccp_call_control_client_get_bearers(client, &bearers); + __ASSERT_NO_MSG(err == 0); + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) && defined(CONFIG_BT_TBS_CLIENT_TBS) + /* If GTBS is enabled then it is at index 0. If the index is not 0, then we decrement it so + * that it can be used as a direct index to the TBS bearers (where index 0 is a TBS inst) + */ + if (index == 0) { + return bearers.gtbs_bearer; + } + index--; +#elif defined(CONFIG_BT_TBS_CLIENT_GTBS) + return bearers.gtbs_bearer; +#endif + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + return bearers.tbs_bearers[index]; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +} + +static int cmd_ccp_call_control_client_read_bearer_name(const struct shell *sh, size_t argc, + char *argv[]) +{ + struct bt_ccp_call_control_client_bearer *bearer; + int 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 %d", index); + + return -ENOEXEC; + } + + err = bt_ccp_call_control_client_read_bearer_provider_name(bearer); + if (err != 0) { + shell_error(sh, "Failed to read bearer[%d] provider name: %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) { @@ -94,6 +202,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE(ccp_call_control_client_cmds, SHELL_CMD_ARG(discover, NULL, "Discover GTBS and TBS on remote device", 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_SUBCMD_SET_END); SHELL_CMD_ARG_REGISTER(ccp_call_control_client, &ccp_call_control_client_cmds, diff --git a/subsys/bluetooth/audio/tbs_internal.h b/subsys/bluetooth/audio/tbs_internal.h index 68b4dbb0e700..3dcfa697e668 100644 --- a/subsys/bluetooth/audio/tbs_internal.h +++ b/subsys/bluetooth/audio/tbs_internal.h @@ -319,6 +319,11 @@ enum bt_tbs_client_flag { BT_TBS_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ }; +/* TODO: The storage of calls, handles and parameters should be moved to the user of the TBS client + * (e.g. the CCP client). This allows for users to use the Zephyr CCP client with static allocation + * or implement their own CCP client or even other profile roles that use the TBS client without + * being restricted to static memory allocation + */ struct bt_tbs_instance { struct bt_tbs_client_call_state calls[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; diff --git a/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt b/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt index 05e846d7878e..2cee4defb0df 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt +++ b/tests/bluetooth/audio/ccp_call_control_client/CMakeLists.txt @@ -14,4 +14,6 @@ target_include_directories(testbinary PRIVATE include) target_sources(testbinary PRIVATE src/main.c + src/test_common.c + src/test_procedures.c ) 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 b29d9bcfde54..55feddbbeaf3 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 @@ -19,5 +19,7 @@ void mock_ccp_call_control_client_cleanup(void); DECLARE_FAKE_VOID_FUNC(mock_ccp_call_control_client_discover_cb, struct bt_ccp_call_control_client *, int, 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 *); #endif /* MOCKS_CCP_CALL_CONTROL_CLIENT_H_ */ diff --git a/tests/bluetooth/audio/ccp_call_control_client/include/test_common.h b/tests/bluetooth/audio/ccp_call_control_client/include/test_common.h new file mode 100644 index 000000000000..9d45b3a94a11 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/include/test_common.h @@ -0,0 +1,17 @@ +/* test_common.h */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +void test_mocks_init(void); +void test_mocks_cleanup(void); + +void test_conn_init(struct bt_conn *conn); diff --git a/tests/bluetooth/audio/ccp_call_control_client/src/main.c b/tests/bluetooth/audio/ccp_call_control_client/src/main.c index c23d6ed592bb..5e407384f7d4 100644 --- a/tests/bluetooth/audio/ccp_call_control_client/src/main.c +++ b/tests/bluetooth/audio/ccp_call_control_client/src/main.c @@ -22,6 +22,7 @@ #include "conn.h" #include "ccp_call_control_client.h" #include "expects_util.h" +#include "test_common.h" DEFINE_FFF_GLOBALS; @@ -35,29 +36,16 @@ struct ccp_call_control_client_test_suite_fixture { static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture) { - mock_ccp_call_control_client_init(); + test_mocks_init(); } static void mock_destroy_rule_after(const struct ztest_unit_test *test, void *fixture) { - mock_ccp_call_control_client_cleanup(); + test_mocks_cleanup(); } ZTEST_RULE(mock_rule, mock_init_rule_before, mock_destroy_rule_after); -static void test_conn_init(struct bt_conn *conn) -{ - conn->index = 0; - conn->info.type = BT_CONN_TYPE_LE; - conn->info.role = BT_CONN_ROLE_CENTRAL; - conn->info.state = BT_CONN_STATE_CONNECTED; - conn->info.security.level = BT_SECURITY_L2; - conn->info.security.enc_key_size = BT_ENC_KEY_SIZE_MAX; - conn->info.security.flags = BT_SECURITY_FLAG_OOB | BT_SECURITY_FLAG_SC; - - mock_bt_conn_connected(conn, BT_HCI_ERR_SUCCESS); -} - static void *ccp_call_control_client_test_suite_setup(void) { struct ccp_call_control_client_test_suite_fixture *fixture; diff --git a/tests/bluetooth/audio/ccp_call_control_client/src/test_common.c b/tests/bluetooth/audio/ccp_call_control_client/src/test_common.c new file mode 100644 index 000000000000..2f45ab61d7de --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/src/test_common.c @@ -0,0 +1,37 @@ +/* common.c - Common functions */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "ccp_call_control_client.h" +#include "conn.h" + +void test_mocks_init(const struct ztest_unit_test *test, void *fixture) +{ + mock_ccp_call_control_client_init(); +} + +void test_mocks_cleanup(const struct ztest_unit_test *test, void *fixture) +{ + mock_ccp_call_control_client_cleanup(); +} + +void test_conn_init(struct bt_conn *conn) +{ + conn->index = 0; + conn->info.type = BT_CONN_TYPE_LE; + conn->info.role = BT_CONN_ROLE_CENTRAL; + conn->info.state = BT_CONN_STATE_CONNECTED; + conn->info.security.level = BT_SECURITY_L2; + conn->info.security.enc_key_size = BT_ENC_KEY_SIZE_MAX; + conn->info.security.flags = BT_SECURITY_FLAG_OOB | BT_SECURITY_FLAG_SC; + + mock_bt_conn_connected(conn, BT_HCI_ERR_SUCCESS); +} 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 new file mode 100644 index 000000000000..98564f984ace --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_client/src/test_procedures.c @@ -0,0 +1,165 @@ +/* test_procedures.c - Testing of CCP procedures */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conn.h" +#include "ccp_call_control_client.h" +#include "expects_util.h" +#include "test_common.h" + +struct ccp_call_control_client_procedures_test_suite_fixture { + /** Need 1 additional bearer than the max to trigger some corner cases */ + struct bt_ccp_call_control_client_bearer + *bearers[CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT]; + struct bt_ccp_call_control_client *client; + struct bt_conn conn; +}; + +static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture) +{ + test_mocks_init(); +} + +static void mock_destroy_rule_after(const struct ztest_unit_test *test, void *fixture) +{ + test_mocks_cleanup(); +} + +ZTEST_RULE(mock_rule, mock_init_rule_before, mock_destroy_rule_after); + +static void *ccp_call_control_client_procedures_test_suite_setup(void) +{ + struct ccp_call_control_client_procedures_test_suite_fixture *fixture; + + fixture = malloc(sizeof(*fixture)); + zassert_not_null(fixture); + + return fixture; +} + +static void ccp_call_control_client_procedures_test_suite_before(void *f) +{ + struct ccp_call_control_client_procedures_test_suite_fixture *fixture = f; + struct bt_ccp_call_control_client_bearers *bearers; + size_t i = 0U; + int err; + + memset(fixture, 0, sizeof(*fixture)); + test_conn_init(&fixture->conn); + + err = bt_ccp_call_control_client_register_cb(&mock_ccp_call_control_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_call_control_client_discover(&fixture->conn, &fixture->client); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_ccp_call_control_client_cb.discover", 1, + mock_ccp_call_control_client_discover_cb_fake.call_count); + zassert_not_null(mock_ccp_call_control_client_discover_cb_fake.arg0_history[0]); + zassert_equal(0, mock_ccp_call_control_client_discover_cb_fake.arg1_history[0]); + bearers = mock_ccp_call_control_client_discover_cb_fake.arg2_history[0]; + zassert_not_null(bearers); + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + zassert_not_null(bearers->gtbs_bearer); + fixture->bearers[i++] = bearers->gtbs_bearer; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + zassert_equal(CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES, bearers->tbs_count); + zassert_not_null(bearers->tbs_bearers); + for (; i < bearers->tbs_count; i++) { + zassert_not_null(bearers->tbs_bearers[i]); + fixture->bearers[i] = bearers->gtbs_bearer; + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +} + +static void ccp_call_control_client_procedures_test_suite_after(void *f) +{ + struct ccp_call_control_client_procedures_test_suite_fixture *fixture = f; + + (void)bt_ccp_call_control_client_unregister_cb(&mock_ccp_call_control_client_cb); + mock_bt_conn_disconnected(&fixture->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); +} + +static void ccp_call_control_client_procedures_test_suite_teardown(void *f) +{ + free(f); +} + +ZTEST_SUITE(ccp_call_control_client_procedures_test_suite, NULL, + ccp_call_control_client_procedures_test_suite_setup, + ccp_call_control_client_procedures_test_suite_before, + ccp_call_control_client_procedures_test_suite_after, + ccp_call_control_client_procedures_test_suite_teardown); + +static ZTEST_F(ccp_call_control_client_procedures_test_suite, + test_ccp_call_control_client_read_bearer_provider_name) +{ + int err; + + err = bt_ccp_call_control_client_read_bearer_provider_name(fixture->bearers[0]); + zassert_equal(err, 0, "Unexpected return value %d", err); + + zexpect_call_count("bt_ccp_call_control_client_cb.bearer_provider_name", 1, + mock_ccp_call_control_client_bearer_provider_name_cb_fake.call_count); + zassert_not_null(mock_ccp_call_control_client_bearer_provider_name_cb_fake + .arg0_history[0]); /* bearer */ + zassert_equal(0, mock_ccp_call_control_client_bearer_provider_name_cb_fake + .arg1_history[0]); /* err */ + zassert_not_null(mock_ccp_call_control_client_bearer_provider_name_cb_fake + .arg2_history[0]); /* name */ +} + +static ZTEST_F(ccp_call_control_client_procedures_test_suite, + test_ccp_call_control_client_read_bearer_provider_name_inval_null_bearer) +{ + int err; + + err = bt_ccp_call_control_client_read_bearer_provider_name(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_provider_name_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_provider_name(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_provider_name_inval_bearer) +{ + struct bt_ccp_call_control_client_bearer *invalid_bearer = + (struct bt_ccp_call_control_client_bearer *)0xdeadbeefU; + int err; + + err = bt_ccp_call_control_client_read_bearer_provider_name(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 7a3ba172218d..8381464c1806 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 @@ -15,9 +15,16 @@ DEFINE_FAKE_VOID_FUNC(mock_ccp_call_control_client_discover_cb, struct bt_ccp_call_control_client *, int, struct bt_ccp_call_control_client_bearers *); +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +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 */ 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 */ }; 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 87564da45875..9eb86f878373 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 @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include @@ -32,3 +33,16 @@ int bt_tbs_client_discover(struct bt_conn *conn) return 0; } + +int bt_tbs_client_read_bearer_provider_name(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_provider_name(conn, 0, inst_index, "bearer name"); + } + + return 0; +} 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 59f5a1b28207..5631b8cb0ec8 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 @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include #include @@ -22,8 +23,10 @@ LOG_MODULE_REGISTER(ccp_call_control_client, CONFIG_LOG_DEFAULT_LEVEL); extern enum bst_result_t bst_result; CREATE_FLAG(flag_discovery_complete); +CREATE_FLAG(flag_bearer_name_read); -static struct bt_ccp_call_control_client *inst; +static struct bt_ccp_call_control_client *call_control_client; +static struct bt_ccp_call_control_client_bearers client_bearers; static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_client *client, int err, struct bt_ccp_call_control_client_bearers *bearers) @@ -41,16 +44,33 @@ static void ccp_call_control_client_discover_cb(struct bt_ccp_call_control_clien return; } + memcpy(&client_bearers, bearers, sizeof(client_bearers)); + SET_FLAG(flag_discovery_complete); } +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void ccp_call_control_client_read_bearer_provider_name_cb( + struct bt_ccp_call_control_client_bearer *bearer, int err, const char *name) +{ + if (err != 0) { + FAIL("Failed to read bearer %p provider name: %d\n", (void *)bearer, err); + return; + } + + LOG_INF("Bearer %p provider name: %s", (void *)bearer, name); + + SET_FLAG(flag_bearer_name_read); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + static void discover_tbs(void) { int err; UNSET_FLAG(flag_discovery_complete); - err = bt_ccp_call_control_client_discover(default_conn, &inst); + err = bt_ccp_call_control_client_discover(default_conn, &call_control_client); if (err) { FAIL("Failed to discover TBS: %d", err); return; @@ -59,10 +79,41 @@ static void discover_tbs(void) WAIT_FOR_FLAG(flag_discovery_complete); } +static void read_bearer_name(struct bt_ccp_call_control_client_bearer *bearer) +{ + int err; + + UNSET_FLAG(flag_bearer_name_read); + + err = bt_ccp_call_control_client_read_bearer_provider_name(bearer); + if (err != 0) { + FAIL("Failed to read name of bearer %p: %d", bearer, err); + return; + } + + WAIT_FOR_FLAG(flag_bearer_name_read); +} + +static void read_bearer_names(void) +{ +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + read_bearer_name(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]); + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +} + 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 +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ }; int err; @@ -95,6 +146,10 @@ 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(); + } + err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); if (err != 0) { FAIL("Failed to disconnect: %d\n", err); diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c index 84c550b878bc..ccf6f42a8561 100644 --- a/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_client/src/test_main.c @@ -37,10 +37,10 @@ static void test_ccp_call_control_client_sample_init(void) static void test_ccp_call_control_client_sample_tick(bs_time_t HW_device_time) { - extern struct bt_ccp_call_control_client *client; + extern struct bt_ccp_call_control_client *call_control_client; - /* If discovery was a success then client is non-NULL - Use as pass criteria */ - if (client == NULL) { + /* If discovery was a success then call_control_client is non-NULL - Use as pass criteria */ + if (call_control_client == NULL) { FAIL("CCP Call Control Client sample FAILED (Did not pass after %i seconds)\n", WAIT_TIME); } else {