From 48b562b00e6f04dec327fbcc716e2b62bfb43e5c Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Wed, 2 Oct 2024 10:12:19 +0200 Subject: [PATCH] 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 8aab2829ded49..e5f81db914ad7 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 c57432c9cf6b1..0dd68befe2961 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 85549f2c321c7..e9efbf1fdf702 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 db8341204a47f..beca758a8600e 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 5de8926fd95a2..9b1e5283cee22 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) { __ASSERT(bt_conn_is_type(conn, BT_CONN_TYPE_LE), "Invalid connection type for %p", conn); @@ -86,6 +113,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)); } } @@ -259,3 +288,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 8f1b210d458e9..1152d62fe8fa5 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 e8ae47d44d2da..08fadf48745fb 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 05e846d7878e2..2cee4defb0df4 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 b29d9bcfde547..55feddbbeaf35 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 0000000000000..9d45b3a94a11a --- /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 c23d6ed592bb4..5e407384f7d45 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 0000000000000..2f45ab61d7de3 --- /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 0000000000000..98564f984aceb --- /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 7a3ba172218d4..8381464c1806c 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 87564da458758..9eb86f878373e 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 59f5a1b282078..5631b8cb0ec83 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 84c550b878bce..ccf6f42a85619 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 {