From 2cd92cd2072e1e12a116e5c4ff4c334d7185cd63 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/api/audio/shell/ccp.rst | 7 + include/zephyr/bluetooth/audio/ccp.h | 33 +++++ samples/bluetooth/ccp_client/prj.conf | 1 + samples/bluetooth/ccp_client/src/main.c | 74 +++++++++- subsys/bluetooth/audio/ccp_client.c | 126 +++++++++++++++++ subsys/bluetooth/audio/shell/ccp_client.c | 114 +++++++++++++++- subsys/bluetooth/audio/tbs_internal.h | 5 + .../bluetooth/audio/ccp_client/CMakeLists.txt | 2 + .../audio/ccp_client/include/ccp_client.h | 2 + .../audio/ccp_client/include/test_common.h | 17 +++ tests/bluetooth/audio/ccp_client/src/main.c | 18 +-- .../audio/ccp_client/src/test_common.c | 37 +++++ .../audio/ccp_client/src/test_procedures.c | 128 ++++++++++++++++++ .../audio/ccp_client/uut/ccp_client.c | 7 + .../audio/ccp_client/uut/tbs_client.c | 14 ++ .../bluetooth/audio/src/ccp_client_test.c | 59 +++++++- 16 files changed, 618 insertions(+), 26 deletions(-) create mode 100644 tests/bluetooth/audio/ccp_client/include/test_common.h create mode 100644 tests/bluetooth/audio/ccp_client/src/test_common.c create mode 100644 tests/bluetooth/audio/ccp_client/src/test_procedures.c diff --git a/doc/connectivity/bluetooth/api/audio/shell/ccp.rst b/doc/connectivity/bluetooth/api/audio/shell/ccp.rst index 30a76341a70d40..a0c224453d8a75 100644 --- a/doc/connectivity/bluetooth/api/audio/shell/ccp.rst +++ b/doc/connectivity/bluetooth/api/audio/shell/ccp.rst @@ -83,3 +83,10 @@ Example Usage when connected uart:~$ ccp_client discover Discovery completed with GTBS and 1 TBS bearers + +.. code-block:: console + + uart:~$ ccp_client read_bearer_name + Bearer 0x20046254 name: Generic TBS + uart:~$ ccp_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 c6aae5b93e27fc..8564ef1a6abbe6 100644 --- a/include/zephyr/bluetooth/audio/ccp.h +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -165,6 +165,23 @@ struct bt_ccp_client_cb { void (*discover)(struct bt_ccp_client *client, int err, struct bt_ccp_client_bearers *bearers); +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + /** + * @brief Callback function for bt_ccp_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 tbs_count Number of Telephone Bearer Service Service instances + * on the remote Call Control Server. + * @param gtbs_found Whether or not the server has a Generic TBS instance. + */ + void (*bearer_provider_name)(struct bt_ccp_client_bearer *bearer, int err, + const char *name); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + /** @internal Internally used field for list handling */ sys_snode_t _node; }; @@ -202,6 +219,22 @@ int bt_ccp_client_unregister_cb(struct bt_ccp_client_cb *cb); */ int bt_ccp_client_get_bearers(struct bt_ccp_client *client, struct bt_ccp_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_client could not be identified for @p bearer + * @retval -EBUSY The @ref bt_ccp_client identified by @p bearer is busy, or the TBS instance + * of @p bearer is busy. + * @retval -ENOTCONN The @ref bt_ccp_client identified by @p bearer is not connected + */ +int bt_ccp_client_read_bearer_provider_name(struct bt_ccp_client_bearer *bearer); /** @} */ /* End of group bt_ccp_client */ #ifdef __cplusplus } diff --git a/samples/bluetooth/ccp_client/prj.conf b/samples/bluetooth/ccp_client/prj.conf index 6b5cec85c17d05..6f11c07ae9ba28 100644 --- a/samples/bluetooth/ccp_client/prj.conf +++ b/samples/bluetooth/ccp_client/prj.conf @@ -14,6 +14,7 @@ CONFIG_BT_CCP_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_client/src/main.c b/samples/bluetooth/ccp_client/src/main.c index 60e1e2a2ef56c7..69d2d1148ac152 100644 --- a/samples/bluetooth/ccp_client/src/main.c +++ b/samples/bluetooth/ccp_client/src/main.c @@ -212,6 +212,21 @@ static void ccp_client_discover_cb(struct bt_ccp_client *client, int err, k_sem_give(&sem_ccp_action_completed); } +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void ccp_client_read_bearer_provider_name_cb(struct bt_ccp_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_client(void) { int err; @@ -264,13 +279,59 @@ static int discover_services(void) return 0; } +static int read_bearer_name(struct bt_ccp_client_bearer *bearer) +{ + int err; + + err = bt_ccp_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_client(void) { - static struct bt_le_scan_cb scan_cbs = { - .recv = scan_recv_cb, - }; static struct bt_ccp_client_cb ccp_client_cbs = { .discover = ccp_client_discover_cb, +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + .bearer_provider_name = ccp_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; @@ -328,6 +389,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_client.c b/subsys/bluetooth/audio/ccp_client.c index f34a8dc17a8c17..2435c2ddde46b0 100644 --- a/subsys/bluetooth/audio/ccp_client.c +++ b/subsys/bluetooth/audio/ccp_client.c @@ -26,6 +26,7 @@ LOG_MODULE_REGISTER(bt_ccp_client, CONFIG_BT_CCP_CLIENT_LOG_LEVEL); static sys_slist_t ccp_client_cbs = SYS_SLIST_STATIC_INIT(&ccp_client_cbs); +static struct bt_tbs_client_cb tbs_client_cbs; static struct bt_tbs_client_cb tbs_client_cbs; @@ -50,6 +51,31 @@ struct bt_ccp_client { static struct bt_ccp_client clients[CONFIG_BT_MAX_CONN]; +static struct bt_ccp_client_bearer *get_bearer_by_tbs_index(struct bt_ccp_client *client, + uint8_t index) +{ + for (size_t i = 0U; i < ARRAY_SIZE(client->bearers); i++) { + struct bt_ccp_client_bearer *bearer = &client->bearers[i]; + + if (bearer->discovered && bearer->tbs_index == index) { + return bearer; + } + } + + return NULL; +} + +static struct bt_ccp_client *get_client_by_bearer(const struct bt_ccp_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_client *get_client_by_conn(const struct bt_conn *conn) { return &clients[bt_conn_index(conn)]; @@ -246,3 +272,103 @@ int bt_ccp_client_get_bearers(struct bt_ccp_client *client, struct bt_ccp_client return 0; } + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void tbs_client_read_string_cb(struct bt_conn *conn, int err, uint8_t inst_index, + const char *name) +{ + struct bt_ccp_client *client = get_client_by_conn(conn); + struct bt_ccp_client_cb *listener, *next; + struct bt_ccp_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_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_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_client_bearer *bearer, + struct bt_ccp_client **client) +{ + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (!bearer->discovered) { + LOG_DBG("bearer %p is not discovered", bearer); + + return -EFAULT; + } + + *client = get_client_by_bearer(bearer); + if (*client == NULL) { + LOG_DBG("Could not identify client from bearer %p", bearer); + + return -EEXIST; + } + + if (atomic_test_and_set_bit((*client)->flags, CCP_CLIENT_FLAG_BUSY)) { + LOG_DBG("Client %p identified by bearer %p is busy", *client, bearer); + + return -EBUSY; + } + + return 0; +} + +int bt_ccp_client_read_bearer_provider_name(struct bt_ccp_client_bearer *bearer) +{ + struct bt_ccp_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_string_cb; + + err = bt_tbs_client_read_bearer_provider_name(client->conn, bearer->tbs_index); + if (err != 0) { + atomic_clear_bit(client->flags, CCP_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_client.c b/subsys/bluetooth/audio/shell/ccp_client.c index f0d578d2e2c9ea..bef57f4f1af1bb 100644 --- a/subsys/bluetooth/audio/shell/ccp_client.c +++ b/subsys/bluetooth/audio/shell/ccp_client.c @@ -16,11 +16,18 @@ #include #include #include +#include +#include #include "host/shell/bt.h" static struct bt_ccp_client *clients[CONFIG_BT_MAX_CONN]; +static struct bt_ccp_client *get_client_by_conn(const struct bt_conn *conn) +{ + return clients[bt_conn_index(conn)]; +} + static void ccp_client_discover_cb(struct bt_ccp_client *client, int err, struct bt_ccp_client_bearers *bearers) { @@ -38,14 +45,33 @@ static void ccp_client_discover_cb(struct bt_ccp_client *client, int err, tbs_count = bearers->tbs_count; #endif /* CONFIG_BT_TBS_CLIENT_TBS */ - shell_info(ctx_shell, "Discovery completed with %s%u TBS bearers", + shell_info(ctx_shell, "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_client_bearer_provider_name_cb(struct bt_ccp_client_bearer *bearer, int err, + const char *name) +{ + if (err != 0) { + shell_error(ctx_shell, "Failed to read bearer %p name: %d", (void *)bearer, err); + return; + } + + shell_info(ctx_shell, "Bearer %p name: %s", (void *)bearer, name); +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +static struct bt_ccp_client_cb ccp_client_cbs = { + .discover = ccp_client_discover_cb, +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + .bearer_provider_name = ccp_client_bearer_provider_name_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +}; + static int cmd_ccp_client_discover(const struct shell *sh, size_t argc, char *argv[]) { static bool cb_registered; - int err; if (default_conn == NULL) { @@ -58,15 +84,13 @@ static int cmd_ccp_client_discover(const struct shell *sh, size_t argc, char *ar } if (!cb_registered) { - static struct bt_ccp_client_cb ccp_client_cbs = { - .discover = ccp_client_discover_cb, - }; - err = bt_ccp_client_register_cb(&ccp_client_cbs); if (err != 0) { shell_error(sh, "Failed to register CCP client cbs (err %d)", err); return -ENOEXEC; } + + cb_registered = true; } err = bt_ccp_client_discover(default_conn, &clients[bt_conn_index(default_conn)]); @@ -79,6 +103,82 @@ static int cmd_ccp_client_discover(const struct shell *sh, size_t argc, char *ar 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_CLIENT_BEARER_COUNT) { + shell_error(sh, "Invalid index: %lu", index); + + return -ENOEXEC; + } + + return (int)index; +} + +static struct bt_ccp_client_bearer *get_bearer_by_index(uint8_t index) +{ + struct bt_ccp_client_bearers bearers; + struct bt_ccp_client *client; + int err; + + client = get_client_by_conn(default_conn); + if (client == NULL) { + return NULL; + } + + err = bt_ccp_client_get_bearers(client, &bearers); + __ASSERT_NO_MSG(err == 0); + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (index == 0) { + return bearers.gtbs_bearer; + } else { + return bearers.tbs_bearers[index - 1]; + } +#else + return bearers.tbs_bearers[index]; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +} + +static int cmd_ccp_client_read_bearer_name(const struct shell *sh, size_t argc, char *argv[]) +{ + struct bt_ccp_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_client_read_bearer_provider_name(bearer); + if (err != 0) { + shell_error(sh, "Failed to read bearer[%lu] provider name: %d", index, err); + + return -ENOEXEC; + } + + return 0; +} + static int cmd_ccp_client(const struct shell *sh, size_t argc, char **argv) { if (argc > 1) { @@ -94,6 +194,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE(ccp_client_cmds, SHELL_CMD_ARG(discover, NULL, "Discover GTBS and TBS on remote device", cmd_ccp_client_discover, 1, 0), + SHELL_CMD_ARG(read_bearer_name, NULL, "Get bearer name [index]", + cmd_ccp_client_read_bearer_name, 1, 1), SHELL_SUBCMD_SET_END); SHELL_CMD_ARG_REGISTER(ccp_client, &ccp_client_cmds, "Bluetooth CCP client shell commands", diff --git a/subsys/bluetooth/audio/tbs_internal.h b/subsys/bluetooth/audio/tbs_internal.h index 68b4dbb0e7004c..3dcfa697e668fb 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_client/CMakeLists.txt b/tests/bluetooth/audio/ccp_client/CMakeLists.txt index c1e663cb2a150f..b47328b1463a9e 100644 --- a/tests/bluetooth/audio/ccp_client/CMakeLists.txt +++ b/tests/bluetooth/audio/ccp_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_client/include/ccp_client.h b/tests/bluetooth/audio/ccp_client/include/ccp_client.h index 6dec763e14720f..5c419ec5bd109a 100644 --- a/tests/bluetooth/audio/ccp_client/include/ccp_client.h +++ b/tests/bluetooth/audio/ccp_client/include/ccp_client.h @@ -20,5 +20,7 @@ void mock_ccp_client_cleanup(void); DECLARE_FAKE_VOID_FUNC(mock_ccp_client_discover_cb, struct bt_ccp_client *, int, struct bt_ccp_client_bearers *); +DECLARE_FAKE_VOID_FUNC(mock_ccp_client_bearer_provider_name_cb, struct bt_ccp_client_bearer *, int, + const char *); #endif /* MOCKS_CCP_CLIENT_H_ */ diff --git a/tests/bluetooth/audio/ccp_client/include/test_common.h b/tests/bluetooth/audio/ccp_client/include/test_common.h new file mode 100644 index 00000000000000..9d45b3a94a11a5 --- /dev/null +++ b/tests/bluetooth/audio/ccp_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_client/src/main.c b/tests/bluetooth/audio/ccp_client/src/main.c index 69050536c74809..ddab0566f46699 100644 --- a/tests/bluetooth/audio/ccp_client/src/main.c +++ b/tests/bluetooth/audio/ccp_client/src/main.c @@ -22,6 +22,7 @@ #include "conn.h" #include "ccp_client.h" #include "expects_util.h" +#include "test_common.h" DEFINE_FFF_GLOBALS; @@ -34,29 +35,16 @@ struct ccp_client_test_suite_fixture { static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture) { - mock_ccp_client_init(); + test_mocks_init(); } static void mock_destroy_rule_after(const struct ztest_unit_test *test, void *fixture) { - mock_ccp_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_client_test_suite_setup(void) { struct ccp_client_test_suite_fixture *fixture; diff --git a/tests/bluetooth/audio/ccp_client/src/test_common.c b/tests/bluetooth/audio/ccp_client/src/test_common.c new file mode 100644 index 00000000000000..7c57ebaa7d4450 --- /dev/null +++ b/tests/bluetooth/audio/ccp_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_client.h" +#include "conn.h" + +void test_mocks_init(const struct ztest_unit_test *test, void *fixture) +{ + mock_ccp_client_init(); +} + +void test_mocks_cleanup(const struct ztest_unit_test *test, void *fixture) +{ + mock_ccp_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_client/src/test_procedures.c b/tests/bluetooth/audio/ccp_client/src/test_procedures.c new file mode 100644 index 00000000000000..b0e74346f58a94 --- /dev/null +++ b/tests/bluetooth/audio/ccp_client/src/test_procedures.c @@ -0,0 +1,128 @@ +/* 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_client.h" +#include "expects_util.h" +#include "test_common.h" + +struct ccp_client_procedures_test_suite_fixture { + /** Need 1 additional bearer than the max to trigger some corner cases */ + struct bt_ccp_client_bearer *bearers[CONFIG_BT_CCP_CLIENT_BEARER_COUNT]; + struct bt_ccp_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_client_procedures_test_suite_setup(void) +{ + struct ccp_client_procedures_test_suite_fixture *fixture; + + fixture = malloc(sizeof(*fixture)); + zassert_not_null(fixture); + + return fixture; +} + +static void discover_cb(struct bt_ccp_client *client, int err, + struct bt_ccp_client_bearer *gtbs_bearer, size_t tbs_count, + struct bt_ccp_client_bearer *tbs_bearers[]); + +static void ccp_client_procedures_test_suite_before(void *f) +{ + struct ccp_client_procedures_test_suite_fixture *fixture = f; + struct bt_ccp_client_bearers *bearers; + size_t i = 0U; + int err; + + memset(fixture, 0, sizeof(*fixture)); + test_conn_init(&fixture->conn); + + err = bt_ccp_client_register_cb(&mock_ccp_client_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_ccp_client_discover(&fixture->conn, &fixture->client); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_ccp_client_cb.discover", 1, + mock_ccp_client_discover_cb_fake.call_count); + zassert_not_null(mock_ccp_client_discover_cb_fake.arg0_history[0]); + zassert_equal(0, mock_ccp_client_discover_cb_fake.arg1_history[0]); + bearers = mock_ccp_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_client_procedures_test_suite_after(void *f) +{ + struct ccp_client_procedures_test_suite_fixture *fixture = f; + + (void)bt_ccp_client_unregister_cb(&mock_ccp_client_cb); + mock_bt_conn_disconnected(&fixture->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); +} + +static void ccp_client_procedures_test_suite_teardown(void *f) +{ + free(f); +} + +ZTEST_SUITE(ccp_client_procedures_test_suite, NULL, ccp_client_procedures_test_suite_setup, + ccp_client_procedures_test_suite_before, ccp_client_procedures_test_suite_after, + ccp_client_procedures_test_suite_teardown); + +static ZTEST_F(ccp_client_procedures_test_suite, test_ccp_client_read_bearer_provider_name) +{ + int err; + + err = bt_ccp_client_read_bearer_provider_name(fixture->bearers[0]); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_ccp_client_cb.bearer_provider_name", 1, + mock_ccp_client_bearer_provider_name_cb_fake.call_count); + zassert_not_null(mock_ccp_client_bearer_provider_name_cb_fake.arg0_history[0]); /* bearer */ + zassert_equal(0, mock_ccp_client_bearer_provider_name_cb_fake.arg1_history[0]); /* err */ + zassert_not_null(mock_ccp_client_bearer_provider_name_cb_fake.arg2_history[0]); /* name */ +} diff --git a/tests/bluetooth/audio/ccp_client/uut/ccp_client.c b/tests/bluetooth/audio/ccp_client/uut/ccp_client.c index 300421c21c4ede..f2bb5b7facd7fe 100644 --- a/tests/bluetooth/audio/ccp_client/uut/ccp_client.c +++ b/tests/bluetooth/audio/ccp_client/uut/ccp_client.c @@ -16,9 +16,16 @@ DEFINE_FAKE_VOID_FUNC(mock_ccp_client_discover_cb, struct bt_ccp_client *, int, struct bt_ccp_client_bearers *); +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +DEFINE_FAKE_VOID_FUNC(mock_ccp_client_bearer_provider_name_cb, struct bt_ccp_client_bearer *, int, + const char *); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ struct bt_ccp_client_cb mock_ccp_client_cb = { .discover = mock_ccp_client_discover_cb, +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + .bearer_provider_name = mock_ccp_client_bearer_provider_name_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ }; void mock_ccp_client_init(void) diff --git a/tests/bluetooth/audio/ccp_client/uut/tbs_client.c b/tests/bluetooth/audio/ccp_client/uut/tbs_client.c index 87564da4587581..9eb86f878373e8 100644 --- a/tests/bluetooth/audio/ccp_client/uut/tbs_client.c +++ b/tests/bluetooth/audio/ccp_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_client_test.c b/tests/bsim/bluetooth/audio/src/ccp_client_test.c index f7b4203ffc9b0e..09e844897a78eb 100644 --- a/tests/bsim/bluetooth/audio/src/ccp_client_test.c +++ b/tests/bsim/bluetooth/audio/src/ccp_client_test.c @@ -5,6 +5,7 @@ */ #include #include +#include #include #include @@ -23,8 +24,10 @@ LOG_MODULE_REGISTER(ccp_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_client *inst; +static struct bt_ccp_client *client; +static struct bt_ccp_client_bearers client_bearers; static void ccp_client_discover_cb(struct bt_ccp_client *client, int err, struct bt_ccp_client_bearers *bearers) @@ -42,16 +45,33 @@ static void ccp_client_discover_cb(struct bt_ccp_client *client, int err, 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_client_read_bearer_provider_name_cb(struct bt_ccp_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_client_discover(default_conn, &inst); + err = bt_ccp_client_discover(default_conn, &client); if (err) { FAIL("Failed to discover TBS: %d", err); return; @@ -60,10 +80,41 @@ static void discover_tbs(void) WAIT_FOR_FLAG(flag_discovery_complete); } +static void read_bearer_name(struct bt_ccp_client_bearer *bearer) +{ + int err; + + UNSET_FLAG(flag_bearer_name_read); + + err = bt_ccp_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_client_cb ccp_client_cbs = { .discover = ccp_client_discover_cb, +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + .bearer_provider_name = ccp_client_read_bearer_provider_name_cb +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ }; int err; @@ -99,6 +150,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);