| /* Bluetooth TBS - Telephone Bearer Service - Client |
| * |
| * Copyright (c) 2020 Bose Corporation |
| * Copyright (c) 2021-2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <zephyr/autoconf.h> |
| #include <zephyr/bluetooth/att.h> |
| #include <zephyr/bluetooth/audio/tbs.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/buf.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/device.h> |
| #include <zephyr/init.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/net_buf.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/atomic.h> |
| #include <zephyr/sys/slist.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/util_macro.h> |
| #include <zephyr/toolchain.h> |
| #include <zephyr/types.h> |
| #include <zephyr/sys/check.h> |
| |
| #include "tbs_internal.h" |
| |
| LOG_MODULE_REGISTER(bt_tbs_client, CONFIG_BT_TBS_CLIENT_LOG_LEVEL); |
| /* TODO TBS client attempts to subscribe to all characteristics at once if the MTU is large enough. |
| * This requires a significant amount of buffers, and should be optimized. |
| */ |
| |
| /* Calculate the required buffers for TBS Client discovery */ |
| #define TBS_CLIENT_BUF_COUNT \ |
| (1 /* Discover buffer */ + 1 /* terminate reason */ + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_INCOMING_URI) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) + \ |
| IS_ENABLED(CONFIG_BT_TBS_CLIENT_INCOMING_CALL)) |
| |
| BUILD_ASSERT(CONFIG_BT_ATT_TX_COUNT >= TBS_CLIENT_BUF_COUNT, "Too few ATT buffers"); |
| |
| #include "common/bt_str.h" |
| |
| struct bt_tbs_server_inst { |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| struct bt_tbs_instance tbs_insts[CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES]; |
| uint8_t inst_cnt; |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| struct bt_tbs_instance gtbs_inst; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_GTBS) */ |
| struct bt_gatt_discover_params discover_params; |
| struct bt_tbs_instance *current_inst; |
| }; |
| |
| static sys_slist_t tbs_client_cbs = SYS_SLIST_STATIC_INIT(&tbs_client_cbs); |
| |
| static struct bt_tbs_server_inst srv_insts[CONFIG_BT_MAX_CONN]; |
| |
| static void discover_next_instance(struct bt_conn *conn); |
| |
| typedef bool (*tbs_instance_find_func_t)(struct bt_tbs_instance *inst, void *user_data); |
| |
| static struct bt_tbs_instance *tbs_instance_find(struct bt_tbs_server_inst *server, |
| tbs_instance_find_func_t func, void *user_data) |
| { |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| if (func(&server->gtbs_inst, user_data)) { |
| return &server->gtbs_inst; |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| for (size_t i = 0; i < server->inst_cnt; i++) { |
| if (func(&server->tbs_insts[i], user_data)) { |
| return &server->tbs_insts[i]; |
| } |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| return NULL; |
| } |
| |
| static struct bt_tbs_instance *tbs_inst_by_index(struct bt_conn *conn, uint8_t index) |
| { |
| struct bt_tbs_server_inst *server; |
| |
| __ASSERT(conn, "NULL conn"); |
| |
| server = &srv_insts[bt_conn_index(conn)]; |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| if (index == BT_TBS_GTBS_INDEX) { |
| return &server->gtbs_inst; |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| if (index < server->inst_cnt) { |
| return &server->tbs_insts[index]; |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| return NULL; |
| } |
| |
| static uint8_t tbs_index(struct bt_conn *conn, const struct bt_tbs_instance *inst) |
| { |
| struct bt_tbs_server_inst *server; |
| ptrdiff_t index = 0; |
| |
| __ASSERT_NO_MSG(conn); |
| __ASSERT_NO_MSG(inst); |
| |
| server = &srv_insts[bt_conn_index(conn)]; |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| if (inst == &server->gtbs_inst) { |
| return BT_TBS_GTBS_INDEX; |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| index = inst - server->tbs_insts; |
| __ASSERT(index >= 0 && index < ARRAY_SIZE(server->tbs_insts), |
| "Invalid bt_tbs_instance pointer"); |
| |
| #else |
| __ASSERT_PRINT("Invalid bt_tbs_instance pointer"); |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| return (uint8_t)index; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) |
| static bool free_call_spot(struct bt_tbs_instance *inst) |
| { |
| for (int i = 0; i < CONFIG_BT_TBS_CLIENT_MAX_CALLS; i++) { |
| if (inst->calls[i].index == BT_TBS_FREE_CALL_INDEX) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ |
| |
| static bool is_instance_handle(struct bt_tbs_instance *inst, void *user_data) |
| { |
| uint16_t handle = POINTER_TO_UINT(user_data); |
| |
| return inst->start_handle <= handle && inst->end_handle >= handle; |
| } |
| |
| static struct bt_tbs_instance *lookup_inst_by_handle(struct bt_conn *conn, |
| uint16_t handle) |
| { |
| uint8_t conn_index; |
| struct bt_tbs_server_inst *srv_inst; |
| struct bt_tbs_instance *inst; |
| |
| __ASSERT(conn, "NULL conn"); |
| |
| conn_index = bt_conn_index(conn); |
| srv_inst = &srv_insts[conn_index]; |
| |
| inst = tbs_instance_find(srv_inst, is_instance_handle, UINT_TO_POINTER(handle)); |
| if (inst != NULL) { |
| return inst; |
| } |
| |
| LOG_DBG("Could not find instance with handle 0x%04x", handle); |
| |
| return NULL; |
| } |
| |
| static uint8_t net_buf_pull_call_state(struct net_buf_simple *buf, |
| struct bt_tbs_client_call_state *call_state) |
| { |
| if (buf->len < sizeof(*call_state)) { |
| LOG_DBG("Invalid buffer length %u", buf->len); |
| return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| |
| call_state->index = net_buf_simple_pull_u8(buf); |
| call_state->state = net_buf_simple_pull_u8(buf); |
| call_state->flags = net_buf_simple_pull_u8(buf); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) |
| static uint8_t net_buf_pull_call(struct net_buf_simple *buf, |
| struct bt_tbs_client_call *call) |
| { |
| const size_t min_item_len = sizeof(call->call_info) + BT_TBS_MIN_URI_LEN; |
| uint8_t item_len; |
| uint8_t uri_len; |
| uint8_t err; |
| uint8_t *uri; |
| |
| __ASSERT(buf, "NULL buf"); |
| __ASSERT(call, "NULL call"); |
| |
| if (buf->len < sizeof(item_len) + min_item_len) { |
| LOG_DBG("Invalid buffer length %u", buf->len); |
| return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| |
| item_len = net_buf_simple_pull_u8(buf); |
| uri_len = item_len - sizeof(call->call_info); |
| |
| if (item_len > buf->len || item_len < min_item_len) { |
| LOG_DBG("Invalid current call item length %u", item_len); |
| return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| |
| err = net_buf_pull_call_state(buf, &call->call_info); |
| if (err != 0) { |
| return err; |
| } |
| |
| uri = net_buf_simple_pull_mem(buf, uri_len); |
| if (uri_len > CONFIG_BT_TBS_MAX_URI_LENGTH) { |
| LOG_WRN("Current call (index %u) uri length larger than supported %u/%zu", |
| call->call_info.index, uri_len, CONFIG_BT_TBS_MAX_URI_LENGTH); |
| return BT_ATT_ERR_INSUFFICIENT_RESOURCES; |
| } |
| |
| (void)memcpy(call->remote_uri, uri, uri_len); |
| call->remote_uri[uri_len] = '\0'; |
| |
| return 0; |
| } |
| |
| static void current_calls_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint8_t call_count, const struct bt_tbs_client_call *calls) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->current_calls != NULL) { |
| listener->current_calls(conn, err, inst_index, call_count, calls); |
| } |
| } |
| } |
| |
| static void bearer_list_current_calls(struct bt_conn *conn, const struct bt_tbs_instance *inst, |
| struct net_buf_simple *buf) |
| { |
| struct bt_tbs_client_call calls[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; |
| char remote_uris[CONFIG_BT_TBS_CLIENT_MAX_CALLS][CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; |
| uint8_t cnt = 0; |
| int err; |
| |
| while (buf->len) { |
| struct bt_tbs_client_call *call; |
| |
| if (cnt == CONFIG_BT_TBS_CLIENT_MAX_CALLS) { |
| LOG_WRN("Could not parse all calls due to memory restrictions"); |
| break; |
| } |
| |
| call = &calls[cnt]; |
| call->remote_uri = remote_uris[cnt]; |
| |
| err = net_buf_pull_call(buf, call); |
| if (err == BT_ATT_ERR_INSUFFICIENT_RESOURCES) { |
| LOG_WRN("Call with skipped due to too long URI"); |
| continue; |
| } else if (err != 0) { |
| LOG_DBG("Invalid current call notification: %d", err); |
| return; |
| } |
| |
| cnt++; |
| } |
| |
| current_calls_changed(conn, 0, tbs_index(conn, inst), cnt, calls); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) |
| static void call_cp_callback_handler(struct bt_conn *conn, int err, |
| uint8_t index, uint8_t opcode, |
| uint8_t call_index) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| LOG_DBG("Status: %s for the %s opcode for call 0x%02x", bt_tbs_status_str(err), |
| bt_tbs_opcode_str(opcode), call_index); |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| switch (opcode) { |
| #if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) |
| case BT_TBS_CALL_OPCODE_ACCEPT: |
| if (listener->accept_call != NULL) { |
| listener->accept_call(conn, err, index, call_index); |
| } |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) |
| case BT_TBS_CALL_OPCODE_TERMINATE: |
| if (listener->terminate_call != NULL) { |
| listener->terminate_call(conn, err, index, call_index); |
| } |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) |
| case BT_TBS_CALL_OPCODE_HOLD: |
| if (listener->hold_call != NULL) { |
| listener->hold_call(conn, err, index, call_index); |
| } |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) |
| case BT_TBS_CALL_OPCODE_RETRIEVE: |
| if (listener->retrieve_call != NULL) { |
| listener->retrieve_call(conn, err, index, call_index); |
| } |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) |
| case BT_TBS_CALL_OPCODE_ORIGINATE: |
| if (listener->originate_call != NULL) { |
| listener->originate_call(conn, err, index, call_index); |
| } |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) |
| case BT_TBS_CALL_OPCODE_JOIN: |
| if (listener->join_calls != NULL) { |
| listener->join_calls(conn, err, index, call_index); |
| } |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ |
| default: |
| break; |
| } |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ |
| |
| const char *parse_string_value(const void *data, uint16_t length, |
| uint16_t max_len) |
| { |
| static char string_val[CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; |
| const size_t len = MIN(length, max_len); |
| |
| if (len != 0) { |
| (void)memcpy(string_val, data, len); |
| } |
| |
| string_val[len] = '\0'; |
| |
| return string_val; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) |
| static void provider_name_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| const char *name) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->bearer_provider_name != NULL) { |
| listener->bearer_provider_name(conn, err, inst_index, name); |
| } |
| } |
| } |
| |
| static void provider_name_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| const char *name = parse_string_value(data, length, |
| CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH); |
| |
| LOG_DBG("%s", name); |
| |
| provider_name_changed(conn, 0, tbs_index(conn, tbs_inst), name); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) |
| static void technology_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint8_t technology) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->technology != NULL) { |
| listener->technology(conn, err, inst_index, technology); |
| } |
| } |
| } |
| |
| static void technology_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| uint8_t technology; |
| |
| LOG_DBG(""); |
| |
| if (length == sizeof(technology)) { |
| (void)memcpy(&technology, data, length); |
| LOG_DBG("%s (0x%02x)", bt_tbs_technology_str(technology), technology); |
| |
| technology_changed(conn, 0, tbs_index(conn, tbs_inst), technology); |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) |
| static void signal_strength_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint8_t signal_strength) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->signal_strength != NULL) { |
| listener->signal_strength(conn, err, inst_index, signal_strength); |
| } |
| } |
| } |
| |
| static void signal_strength_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| uint8_t signal_strength; |
| |
| LOG_DBG(""); |
| |
| if (length == sizeof(signal_strength)) { |
| (void)memcpy(&signal_strength, data, length); |
| LOG_DBG("0x%02x", signal_strength); |
| |
| signal_strength_changed(conn, 0, tbs_index(conn, tbs_inst), signal_strength); |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) |
| static void current_calls_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| struct net_buf_simple buf; |
| |
| LOG_DBG(""); |
| |
| net_buf_simple_init_with_data(&buf, (void *)data, length); |
| |
| /* TODO: If length == MTU, do long read for all calls */ |
| |
| bearer_list_current_calls(conn, tbs_inst, &buf); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) |
| static void status_flags_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint16_t status_flags) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->status_flags != NULL) { |
| listener->status_flags(conn, err, inst_index, status_flags); |
| } |
| } |
| } |
| |
| static void status_flags_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| uint16_t status_flags; |
| |
| LOG_DBG(""); |
| |
| if (length == sizeof(status_flags)) { |
| (void)memcpy(&status_flags, data, length); |
| LOG_DBG("0x%04x", status_flags); |
| |
| status_flags_changed(conn, 0, tbs_index(conn, tbs_inst), status_flags); |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) |
| static void call_uri_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| const char *call_uri) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->call_uri != NULL) { |
| listener->call_uri(conn, err, inst_index, call_uri); |
| } |
| } |
| } |
| |
| static void incoming_uri_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| const char *uri = parse_string_value(data, length, |
| CONFIG_BT_TBS_MAX_URI_LENGTH); |
| |
| LOG_DBG("%s", uri); |
| |
| call_uri_changed(conn, 0, tbs_index(conn, tbs_inst), uri); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ |
| |
| static void call_state_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint8_t call_count, |
| const struct bt_tbs_client_call_state *call_states) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->call_state != NULL) { |
| listener->call_state(conn, err, inst_index, call_count, call_states); |
| } |
| } |
| } |
| |
| static void call_state_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_client_call_state call_states[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; |
| uint8_t cnt = 0; |
| struct net_buf_simple buf; |
| |
| LOG_DBG(""); |
| |
| net_buf_simple_init_with_data(&buf, (void *)data, length); |
| |
| /* TODO: If length == MTU, do long read for all call states */ |
| |
| while (buf.len) { |
| struct bt_tbs_client_call_state *call_state; |
| int err; |
| |
| if (cnt == CONFIG_BT_TBS_CLIENT_MAX_CALLS) { |
| LOG_WRN("Could not parse all calls due to memory restrictions"); |
| break; |
| } |
| |
| call_state = &call_states[cnt]; |
| |
| err = net_buf_pull_call_state(&buf, call_state); |
| if (err != 0) { |
| LOG_DBG("Invalid current call notification: %d", err); |
| return; |
| } |
| |
| cnt++; |
| } |
| |
| call_state_changed(conn, 0, tbs_index(conn, tbs_inst), cnt, call_states); |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) |
| static void call_cp_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_call_cp_notify *ind_val; |
| |
| LOG_DBG(""); |
| |
| if (length == sizeof(*ind_val)) { |
| ind_val = (struct bt_tbs_call_cp_notify *)data; |
| LOG_DBG("Status: %s for the %s opcode for call 0x%02X", |
| bt_tbs_status_str(ind_val->status), bt_tbs_opcode_str(ind_val->opcode), |
| ind_val->call_index); |
| |
| call_cp_callback_handler(conn, ind_val->status, tbs_index(conn, tbs_inst), |
| ind_val->opcode, ind_val->call_index); |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ |
| |
| static void terminate_reason_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| struct bt_tbs_terminate_reason reason) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->termination_reason != NULL) { |
| listener->termination_reason(conn, err, inst_index, reason.call_index, |
| reason.reason); |
| } |
| } |
| } |
| |
| static void termination_reason_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_terminate_reason reason; |
| |
| LOG_DBG(""); |
| |
| if (length == sizeof(reason)) { |
| (void)memcpy(&reason, data, length); |
| LOG_DBG("ID 0x%02X, reason %s", reason.call_index, |
| bt_tbs_term_reason_str(reason.reason)); |
| |
| terminate_reason_changed(conn, 0, tbs_index(conn, tbs_inst), reason); |
| } |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) |
| static void remote_uri_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| const char *remote_uri) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->remote_uri != NULL) { |
| listener->remote_uri(conn, err, inst_index, remote_uri); |
| } |
| } |
| } |
| |
| static void in_call_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| const char *uri = parse_string_value(data, length, |
| CONFIG_BT_TBS_MAX_URI_LENGTH); |
| |
| LOG_DBG("%s", uri); |
| |
| remote_uri_changed(conn, 0, tbs_index(conn, tbs_inst), uri); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) |
| static void friendly_name_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| const char *friendly_name) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->friendly_name != NULL) { |
| listener->friendly_name(conn, err, inst_index, friendly_name); |
| } |
| } |
| } |
| |
| static void friendly_name_notify_handler(struct bt_conn *conn, |
| const struct bt_tbs_instance *tbs_inst, |
| const void *data, uint16_t length) |
| { |
| const char *name = parse_string_value(data, length, |
| CONFIG_BT_TBS_MAX_URI_LENGTH); |
| |
| LOG_DBG("%s", name); |
| |
| friendly_name_changed(conn, 0, tbs_index(conn, tbs_inst), name); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ |
| |
| /** @brief Handles notifications and indications from the server */ |
| static uint8_t notify_handler(struct bt_conn *conn, |
| struct bt_gatt_subscribe_params *params, |
| const void *data, uint16_t length) |
| { |
| uint16_t handle = params->value_handle; |
| struct bt_tbs_instance *tbs_inst; |
| |
| if (data == NULL || conn == NULL) { |
| LOG_DBG("[UNSUBSCRIBED] 0x%04X", params->value_handle); |
| params->value_handle = 0U; |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| tbs_inst = lookup_inst_by_handle(conn, handle); |
| if (tbs_inst != NULL) { |
| uint8_t inst_index = tbs_index(conn, tbs_inst); |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| LOG_HEXDUMP_DBG(data, length, "notify handler value"); |
| |
| if (handle == tbs_inst->call_state_sub_params.value_handle) { |
| call_state_notify_handler(conn, tbs_inst, data, length); |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) |
| } else if (handle == tbs_inst->name_sub_params.value_handle) { |
| provider_name_notify_handler(conn, tbs_inst, data, |
| length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) |
| } else if (handle == tbs_inst->technology_sub_params.value_handle) { |
| technology_notify_handler(conn, tbs_inst, data, length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) |
| } else if (handle == tbs_inst->signal_strength_sub_params.value_handle) { |
| signal_strength_notify_handler(conn, tbs_inst, data, |
| length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) |
| } else if (handle == tbs_inst->status_flags_sub_params.value_handle) { |
| status_flags_notify_handler(conn, tbs_inst, data, |
| length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) |
| } else if (handle == tbs_inst->current_calls_sub_params.value_handle) { |
| current_calls_notify_handler(conn, tbs_inst, data, |
| length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) |
| } else if (handle == tbs_inst->in_target_uri_sub_params.value_handle) { |
| incoming_uri_notify_handler(conn, tbs_inst, data, |
| length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) |
| } else if (handle == tbs_inst->call_cp_sub_params.value_handle) { |
| call_cp_notify_handler(conn, tbs_inst, data, length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ |
| } else if (handle == tbs_inst->termination_reason_handle) { |
| termination_reason_notify_handler(conn, tbs_inst, data, |
| length); |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) |
| } else if (handle == tbs_inst->incoming_call_sub_params.value_handle) { |
| in_call_notify_handler(conn, tbs_inst, data, length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) |
| } else if (handle == tbs_inst->friendly_name_sub_params.value_handle) { |
| friendly_name_notify_handler(conn, tbs_inst, data, |
| length); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ |
| } |
| } else { |
| LOG_DBG("Notification/Indication on unknown TBS inst"); |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| |
| static void initialize_net_buf_read_buffer(struct bt_tbs_instance *inst) |
| { |
| net_buf_simple_init_with_data(&inst->net_buf, &inst->read_buf, |
| sizeof(inst->read_buf)); |
| net_buf_simple_reset(&inst->net_buf); |
| } |
| |
| static void tbs_client_gatt_read_complete(struct bt_tbs_instance *inst) |
| { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| atomic_clear_bit(inst->flags, BT_TBS_CLIENT_FLAG_BUSY); |
| } |
| |
| static int tbs_client_gatt_read(struct bt_conn *conn, struct bt_tbs_instance *inst, uint16_t handle, |
| bt_gatt_read_func_t func) |
| { |
| int err; |
| |
| if (atomic_test_and_set_bit(inst->flags, BT_TBS_CLIENT_FLAG_BUSY)) { |
| LOG_DBG("Instance is busy"); |
| |
| return -EBUSY; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = func; |
| inst->read_params.handle_count = 1U; |
| inst->read_params.single.handle = handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| tbs_client_gatt_read_complete(inst); |
| |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static bool gtbs_found(struct bt_tbs_server_inst *srv_inst) |
| { |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| return srv_inst->gtbs_inst.start_handle != 0; |
| #else |
| return false; |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| } |
| |
| static uint8_t inst_cnt(struct bt_tbs_server_inst *srv_inst) |
| { |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| return srv_inst->inst_cnt; |
| #else |
| return 0; |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| } |
| |
| static void tbs_client_discover_complete(struct bt_conn *conn, int err) |
| { |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; |
| struct bt_tbs_client_cb *listener, *next; |
| |
| LOG_DBG("conn %p err %d", (void *)conn, err); |
| |
| /* Clear the current instance in discovery */ |
| srv_inst->current_inst = NULL; |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| atomic_clear_bit(srv_inst->gtbs_inst.flags, BT_TBS_CLIENT_FLAG_BUSY); |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| for (size_t i = 0U; i < ARRAY_SIZE(srv_inst->tbs_insts); i++) { |
| atomic_clear_bit(srv_inst->tbs_insts[i].flags, BT_TBS_CLIENT_FLAG_BUSY); |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->discover != NULL) { |
| listener->discover(conn, err, inst_cnt(srv_inst), gtbs_found(srv_inst)); |
| } |
| } |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) || \ |
| defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) || \ |
| defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) || \ |
| defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) || \ |
| defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) || \ |
| defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) |
| |
| static bool can_add_string_to_net_buf(const struct net_buf_simple *buf, size_t len) |
| { |
| return buf->len + len + sizeof('\0') <= buf->size; |
| } |
| |
| /* Common function to read tbs_client strings which may require long reads */ |
| static uint8_t handle_string_long_read(struct bt_tbs_instance *inst, uint8_t err, const void *data, |
| uint16_t offset, uint16_t length, bool truncatable) |
| { |
| if (err != 0) { |
| LOG_DBG("err: %u", err); |
| |
| return BT_GATT_ERR(err); |
| } |
| |
| if (data != NULL) { |
| /* Get data and try to read more using read long procedure */ |
| LOG_DBG("Read (offset %u): %s", offset, bt_hex(data, length)); |
| |
| if (!can_add_string_to_net_buf(&inst->net_buf, length)) { |
| LOG_DBG("Read length %u: String buffer full", length); |
| if (truncatable) { |
| /* Use the remaining buffer and stop reading and leave room for NULL |
| * terminator |
| */ |
| LOG_DBG("Truncating string"); |
| length = net_buf_simple_tailroom(&inst->net_buf) - sizeof('\0'); |
| net_buf_simple_add_mem(&inst->net_buf, data, length); |
| |
| /* Ensure that the data is correctly truncated */ |
| utf8_trunc(inst->net_buf.data); |
| } else { |
| return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); |
| } |
| } else { |
| net_buf_simple_add_mem(&inst->net_buf, data, length); |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME || \ |
| * CONFIG_BT_TBS_CLIENT_BEARER_UCI || \ |
| * CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST || \ |
| * CONFIG_BT_TBS_CLIENT_INCOMING_URI || \ |
| * CONFIG_BT_TBS_CLIENT_INCOMING_CALL || \ |
| * CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME \ |
| */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) |
| static int tbs_client_common_call_control(struct bt_conn *conn, |
| uint8_t inst_index, |
| uint8_t call_index, |
| uint8_t opcode) |
| { |
| struct bt_tbs_instance *inst; |
| struct bt_tbs_call_cp_acc common; |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->call_cp_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| common.opcode = opcode; |
| common.call_index = call_index; |
| |
| return bt_gatt_write_without_response(conn, inst->call_cp_sub_params.value_handle, |
| &common, sizeof(common), false); |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_CP_PROCEDURES */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) |
| static uint8_t read_bearer_provider_name_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| const uint8_t inst_index = tbs_index(conn, inst); |
| int ret; |
| |
| LOG_DBG(""); |
| |
| ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); |
| if (ret != BT_GATT_ITER_CONTINUE) { |
| if (ret == BT_GATT_ITER_STOP) { |
| /* At this point the inst->net_buf.data contains a NULL terminator string */ |
| provider_name_changed(conn, 0, inst_index, (char *)inst->net_buf.data); |
| } else { |
| provider_name_changed(conn, ret, inst_index, NULL); |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) |
| static void bearer_uci_changed(struct bt_conn *conn, int err, uint8_t inst_index, const char *uci) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->bearer_uci != NULL) { |
| listener->bearer_uci(conn, err, inst_index, uci); |
| } |
| } |
| } |
| |
| static uint8_t read_bearer_uci_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| const uint8_t inst_index = tbs_index(conn, inst); |
| int ret; |
| |
| LOG_DBG(""); |
| |
| ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); |
| if (ret != BT_GATT_ITER_CONTINUE) { |
| if (ret == BT_GATT_ITER_STOP) { |
| /* At this point the inst->net_buf.data contains a NULL terminator string */ |
| bearer_uci_changed(conn, 0, inst_index, (char *)inst->net_buf.data); |
| } else { |
| bearer_uci_changed(conn, ret, inst_index, NULL); |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) |
| static uint8_t read_technology_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cb_err = err; |
| uint8_t technology = 0; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (err != 0) { |
| LOG_DBG("err: 0x%02X", err); |
| } else if (data != NULL) { |
| LOG_HEXDUMP_DBG(data, length, "Data read"); |
| if (length == sizeof(technology)) { |
| (void)memcpy(&technology, data, length); |
| LOG_DBG("%s (0x%02x)", bt_tbs_technology_str(technology), technology); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| technology_changed(conn, cb_err, inst_index, technology); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) |
| static void uri_list_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| const char *uri_list) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->uri_list != NULL) { |
| listener->uri_list(conn, err, inst_index, uri_list); |
| } |
| } |
| } |
| |
| static uint8_t read_uri_list_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| const uint8_t inst_index = tbs_index(conn, inst); |
| int ret; |
| |
| LOG_DBG(""); |
| |
| ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); |
| if (ret != BT_GATT_ITER_CONTINUE) { |
| if (ret == BT_GATT_ITER_STOP) { |
| /* At this point the inst->net_buf.data contains a NULL terminator string */ |
| uri_list_changed(conn, 0, inst_index, (char *)inst->net_buf.data); |
| } else { |
| uri_list_changed(conn, ret, inst_index, NULL); |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) |
| static uint8_t read_signal_strength_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cb_err = err; |
| uint8_t signal_strength = 0; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (err != 0) { |
| LOG_DBG("err: 0x%02X", err); |
| } else if (data != NULL) { |
| LOG_HEXDUMP_DBG(data, length, "Data read"); |
| if (length == sizeof(signal_strength)) { |
| (void)memcpy(&signal_strength, data, length); |
| LOG_DBG("0x%02x", signal_strength); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| signal_strength_changed(conn, cb_err, inst_index, signal_strength); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) |
| static void signal_interval_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint8_t signal_interval) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->signal_interval != NULL) { |
| listener->signal_interval(conn, err, inst_index, signal_interval); |
| } |
| } |
| } |
| |
| static uint8_t read_signal_interval_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cb_err = err; |
| uint8_t signal_interval = 0; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (err != 0) { |
| LOG_DBG("err: 0x%02X", err); |
| } else if (data != NULL) { |
| LOG_HEXDUMP_DBG(data, length, "Data read"); |
| if (length == sizeof(signal_interval)) { |
| (void)memcpy(&signal_interval, data, length); |
| LOG_DBG("0x%02x", signal_interval); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| signal_interval_changed(conn, cb_err, inst_index, signal_interval); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) |
| static uint8_t read_current_calls_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| int tbs_err = err; |
| |
| LOG_DBG("Read bearer list current calls, index %u", inst_index); |
| |
| if ((tbs_err == 0) && (data != NULL) && |
| (net_buf_simple_tailroom(&inst->net_buf) < length)) { |
| tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; |
| } |
| |
| if (tbs_err != 0) { |
| LOG_DBG("err: %d", tbs_err); |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| current_calls_changed(conn, tbs_err, inst_index, 0, NULL); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| if (data != NULL) { |
| LOG_DBG("Current calls read (offset %u): %s", |
| params->single.offset, |
| bt_hex(data, length)); |
| |
| net_buf_simple_add_mem(&inst->net_buf, data, length); |
| |
| /* Returning continue will try to read more using read |
| * long procedure |
| */ |
| return BT_GATT_ITER_CONTINUE; |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| if (inst->net_buf.len == 0) { |
| current_calls_changed(conn, 0, inst_index, 0, NULL); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| bearer_list_current_calls(conn, inst, &inst->net_buf); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CCID) |
| static void ccid_changed(struct bt_conn *conn, int err, uint8_t inst_index, uint8_t ccid) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->ccid != NULL) { |
| listener->ccid(conn, err, inst_index, ccid); |
| } |
| } |
| } |
| |
| static uint8_t read_ccid_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cb_err = err; |
| uint8_t ccid = 0; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (err != 0) { |
| LOG_DBG("err: 0x%02X", err); |
| } else if (data != NULL) { |
| LOG_HEXDUMP_DBG(data, length, "Data read"); |
| if (length == sizeof(ccid)) { |
| (void)memcpy(&ccid, data, length); |
| LOG_DBG("0x%02x", ccid); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| ccid_changed(conn, cb_err, inst_index, ccid); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) |
| static uint8_t read_status_flags_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cb_err = err; |
| uint16_t status_flags = 0; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (err != 0) { |
| LOG_DBG("err: 0x%02X", err); |
| } else if (data != NULL) { |
| LOG_HEXDUMP_DBG(data, length, "Data read"); |
| if (length == sizeof(status_flags)) { |
| (void)memcpy(&status_flags, data, length); |
| LOG_DBG("0x%04x", status_flags); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| status_flags_changed(conn, cb_err, inst_index, status_flags); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) |
| static uint8_t read_call_uri_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| const uint8_t inst_index = tbs_index(conn, inst); |
| int ret; |
| |
| LOG_DBG(""); |
| |
| ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); |
| if (ret != BT_GATT_ITER_CONTINUE) { |
| if (ret == BT_GATT_ITER_STOP) { |
| /* At this point the inst->net_buf.data contains a NULL terminator string */ |
| call_uri_changed(conn, 0, inst_index, (char *)inst->net_buf.data); |
| } else { |
| call_uri_changed(conn, ret, inst_index, NULL); |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ |
| |
| static uint8_t read_call_state_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cnt = 0; |
| struct bt_tbs_client_call_state call_states[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; |
| int tbs_err = err; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if ((tbs_err == 0) && (data != NULL) && |
| (net_buf_simple_tailroom(&inst->net_buf) < length)) { |
| tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; |
| } |
| |
| if (tbs_err != 0) { |
| LOG_DBG("err: %d", tbs_err); |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| call_state_changed(conn, tbs_err, inst_index, 0, NULL); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| if (data != NULL) { |
| LOG_DBG("Call states read (offset %u): %s", params->single.offset, |
| bt_hex(data, length)); |
| |
| net_buf_simple_add_mem(&inst->net_buf, data, length); |
| |
| /* Returning continue will try to read more using read long procedure */ |
| return BT_GATT_ITER_CONTINUE; |
| } |
| |
| if (inst->net_buf.len == 0) { |
| tbs_client_gatt_read_complete(inst); |
| |
| call_state_changed(conn, 0, inst_index, 0, NULL); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| /* Finished reading, start parsing */ |
| while (inst->net_buf.len != 0) { |
| struct bt_tbs_client_call_state *call_state; |
| |
| if (cnt == CONFIG_BT_TBS_CLIENT_MAX_CALLS) { |
| LOG_WRN("Could not parse all calls due to memory restrictions"); |
| break; |
| } |
| |
| call_state = &call_states[cnt]; |
| |
| tbs_err = net_buf_pull_call_state(&inst->net_buf, call_state); |
| if (tbs_err != 0) { |
| LOG_DBG("Invalid current call notification: %d", err); |
| break; |
| } |
| |
| cnt++; |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| call_state_changed(conn, tbs_err, inst_index, cnt, call_states); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) |
| static void optional_opcodes_changed(struct bt_conn *conn, int err, uint8_t inst_index, |
| uint16_t optional_opcodes) |
| { |
| struct bt_tbs_client_cb *listener, *next; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&tbs_client_cbs, listener, next, _node) { |
| if (listener->optional_opcodes != NULL) { |
| listener->optional_opcodes(conn, err, inst_index, optional_opcodes); |
| } |
| } |
| } |
| |
| static uint8_t read_optional_opcodes_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| uint8_t cb_err = err; |
| uint16_t optional_opcodes = 0; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (err != 0) { |
| LOG_DBG("err: 0x%02X", err); |
| } else if (data != NULL) { |
| LOG_HEXDUMP_DBG(data, length, "Data read"); |
| if (length == sizeof(optional_opcodes)) { |
| (void)memcpy(&optional_opcodes, data, length); |
| LOG_DBG("0x%04x", optional_opcodes); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| optional_opcodes_changed(conn, cb_err, inst_index, optional_opcodes); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) |
| static uint8_t read_remote_uri_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| const uint8_t inst_index = tbs_index(conn, inst); |
| int ret; |
| |
| LOG_DBG(""); |
| |
| ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); |
| if (ret != BT_GATT_ITER_CONTINUE) { |
| if (ret == BT_GATT_ITER_STOP) { |
| /* At this point the inst->net_buf.data contains a NULL terminator string */ |
| remote_uri_changed(conn, 0, inst_index, (char *)inst->net_buf.data); |
| } else { |
| remote_uri_changed(conn, ret, inst_index, NULL); |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) |
| static uint8_t read_friendly_name_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| const uint8_t inst_index = tbs_index(conn, inst); |
| int ret; |
| |
| LOG_DBG(""); |
| |
| ret = handle_string_long_read(inst, err, data, params->single.offset, length, true); |
| if (ret != BT_GATT_ITER_CONTINUE) { |
| if (ret == BT_GATT_ITER_STOP) { |
| /* At this point the inst->net_buf.data contains a NULL terminator string */ |
| friendly_name_changed(conn, 0, inst_index, (char *)inst->net_buf.data); |
| } else { |
| friendly_name_changed(conn, ret, inst_index, NULL); |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CCID) |
| static uint8_t disc_read_ccid_cb(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, uint16_t length) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, read_params); |
| uint8_t inst_index = tbs_index(conn, inst); |
| int cb_err = err; |
| |
| LOG_DBG("Index %u", inst_index); |
| |
| if (cb_err != 0) { |
| LOG_DBG("err: 0x%02X", cb_err); |
| } else if (data != NULL) { |
| if (length == sizeof(inst->ccid)) { |
| inst->ccid = ((uint8_t *)data)[0]; |
| LOG_DBG("0x%02x", inst->ccid); |
| } else { |
| LOG_DBG("Invalid length"); |
| cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; |
| } |
| } |
| |
| tbs_client_gatt_read_complete(inst); |
| |
| if (cb_err != 0) { |
| tbs_client_discover_complete(conn, cb_err); |
| } else { |
| discover_next_instance(conn); |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| static void tbs_client_disc_read_ccid(struct bt_conn *conn) |
| { |
| const uint8_t conn_index = bt_conn_index(conn); |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; |
| struct bt_tbs_instance *inst = srv_inst->current_inst; |
| int err; |
| |
| err = tbs_client_gatt_read(conn, inst, inst->ccid_handle, disc_read_ccid_cb); |
| if (err != 0) { |
| tbs_client_discover_complete(conn, err); |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |
| |
| /** |
| * @brief This will discover all characteristics on the server, retrieving the |
| * handles of the writeable characteristics and subscribing to all notify and |
| * indicate characteristics. |
| */ |
| static uint8_t discover_func(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, |
| struct bt_gatt_discover_params *params) |
| { |
| const uint8_t conn_index = bt_conn_index(conn); |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; |
| struct bt_tbs_instance *current_inst = srv_inst->current_inst; |
| |
| if (attr == NULL) { |
| #if defined(CONFIG_BT_TBS_CLIENT_CCID) |
| /* Read the CCID as the last part of discovering a TBS instance */ |
| tbs_client_disc_read_ccid(conn); |
| #else |
| discover_next_instance(conn); |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); |
| |
| if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) { |
| const struct bt_gatt_chrc *chrc; |
| struct bt_gatt_subscribe_params *sub_params = NULL; |
| |
| chrc = (struct bt_gatt_chrc *)attr->user_data; |
| |
| if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_CALL_STATE) == 0) { |
| LOG_DBG("Call state"); |
| sub_params = ¤t_inst->call_state_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->call_state_sub_disc_params; |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_PROVIDER_NAME) == 0) { |
| LOG_DBG("Provider name"); |
| sub_params = ¤t_inst->name_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->name_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_UCI) == 0) { |
| LOG_DBG("Bearer UCI"); |
| current_inst->bearer_uci_handle = chrc->value_handle; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_TECHNOLOGY) == 0) { |
| LOG_DBG("Technology"); |
| sub_params = ¤t_inst->technology_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->technology_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_URI_LIST) == 0) { |
| LOG_DBG("URI Scheme List"); |
| current_inst->uri_list_handle = chrc->value_handle; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_SIGNAL_STRENGTH) == 0) { |
| LOG_DBG("Signal strength"); |
| sub_params = ¤t_inst->signal_strength_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->signal_strength_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) \ |
| || defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_SIGNAL_INTERVAL) == 0) { |
| LOG_DBG("Signal strength reporting interval"); |
| current_inst->signal_interval_handle = chrc->value_handle; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ |
| /* || defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_LIST_CURRENT_CALLS) == 0) { |
| LOG_DBG("Current calls"); |
| sub_params = ¤t_inst->current_calls_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->current_calls_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_CCID) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_CCID) == 0) { |
| LOG_DBG("CCID"); |
| current_inst->ccid_handle = chrc->value_handle; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_INCOMING_URI) == 0) { |
| LOG_DBG("Incoming target URI"); |
| sub_params = ¤t_inst->in_target_uri_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->in_target_uri_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_STATUS_FLAGS) == 0) { |
| LOG_DBG("Status flags"); |
| sub_params = ¤t_inst->status_flags_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->status_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_CALL_CONTROL_POINT) == 0) { |
| LOG_DBG("Call control point"); |
| sub_params = ¤t_inst->call_cp_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->call_cp_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_OPTIONAL_OPCODES) == 0) { |
| LOG_DBG("Supported opcodes"); |
| current_inst->optional_opcodes_handle = chrc->value_handle; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_TERMINATE_REASON) == 0) { |
| LOG_DBG("Termination reason"); |
| current_inst->termination_reason_handle = chrc->value_handle; |
| sub_params = ¤t_inst->termination_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->termination_sub_disc_params; |
| #if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_FRIENDLY_NAME) == 0) { |
| LOG_DBG("Incoming friendly name"); |
| sub_params = ¤t_inst->friendly_name_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->friendly_name_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) |
| } else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_INCOMING_CALL) == 0) { |
| LOG_DBG("Incoming call"); |
| sub_params = ¤t_inst->incoming_call_sub_params; |
| sub_params->value_handle = chrc->value_handle; |
| sub_params->disc_params = ¤t_inst->incoming_call_sub_disc_params; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ |
| } |
| |
| if (sub_params != NULL) { |
| sub_params->value = 0; |
| if (chrc->properties & BT_GATT_CHRC_NOTIFY) { |
| sub_params->value = BT_GATT_CCC_NOTIFY; |
| } else if (chrc->properties & BT_GATT_CHRC_INDICATE) { |
| sub_params->value = BT_GATT_CCC_INDICATE; |
| } |
| |
| if (sub_params->value != 0) { |
| int err; |
| |
| sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; |
| sub_params->end_handle = current_inst->end_handle; |
| sub_params->notify = notify_handler; |
| atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); |
| |
| err = bt_gatt_subscribe(conn, sub_params); |
| if (err != 0 && err != -EALREADY) { |
| LOG_DBG("Could not subscribe to " |
| "characteristic at handle 0x%04X" |
| "(%d)", |
| sub_params->value_handle, err); |
| tbs_client_discover_complete(conn, err); |
| |
| return BT_GATT_ITER_STOP; |
| } else { |
| LOG_DBG("Subscribed to characteristic at " |
| "handle 0x%04X", |
| sub_params->value_handle); |
| } |
| } |
| } |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| |
| static struct bt_tbs_instance *get_next_instance(struct bt_conn *conn, |
| struct bt_tbs_server_inst *srv_inst) |
| { |
| uint8_t inst_index; |
| |
| if (srv_inst->current_inst != NULL) { |
| inst_index = tbs_index(conn, srv_inst->current_inst); |
| if (inst_index == BT_TBS_GTBS_INDEX) { |
| inst_index = 0; |
| } else { |
| inst_index++; |
| } |
| |
| return tbs_inst_by_index(conn, inst_index); |
| } |
| |
| inst_index = gtbs_found(srv_inst) ? BT_TBS_GTBS_INDEX : 0; |
| |
| return tbs_inst_by_index(conn, inst_index); |
| } |
| |
| static void discover_next_instance(struct bt_conn *conn) |
| { |
| int err; |
| uint8_t conn_index = bt_conn_index(conn); |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; |
| |
| srv_inst->current_inst = get_next_instance(conn, srv_inst); |
| if (srv_inst->current_inst == NULL) { |
| tbs_client_discover_complete(conn, 0); |
| return; |
| } |
| |
| LOG_DBG("inst_index %u", tbs_index(conn, srv_inst->current_inst)); |
| |
| (void)memset(&srv_inst->discover_params, 0, sizeof(srv_inst->discover_params)); |
| srv_inst->discover_params.uuid = NULL; |
| srv_inst->discover_params.start_handle = srv_inst->current_inst->start_handle; |
| srv_inst->discover_params.end_handle = srv_inst->current_inst->end_handle; |
| srv_inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; |
| srv_inst->discover_params.func = discover_func; |
| |
| err = bt_gatt_discover(conn, &srv_inst->discover_params); |
| if (err != 0) { |
| tbs_client_discover_complete(conn, err); |
| } |
| } |
| |
| static void primary_discover_complete(struct bt_tbs_server_inst *server, struct bt_conn *conn) |
| { |
| if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS)) { |
| LOG_DBG("Discover complete, found %u instances (GTBS%s found)", |
| inst_cnt(server), gtbs_found(server) ? "" : " not"); |
| } else { |
| LOG_DBG("Discover complete, found %u instances", inst_cnt(server)); |
| } |
| |
| server->current_inst = NULL; |
| |
| if (gtbs_found(server) || inst_cnt(server) > 0) { |
| discover_next_instance(conn); |
| } else { |
| tbs_client_discover_complete(conn, 0); |
| } |
| } |
| |
| /** |
| * @brief This will discover all characteristics on the server, retrieving the |
| * handles of the writeable characteristics and subscribing to all notify and |
| * indicate characteristics. |
| */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| static const struct bt_uuid *tbs_uuid = BT_UUID_TBS; |
| |
| static uint8_t primary_discover_tbs_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| struct bt_gatt_discover_params *params) |
| { |
| const uint8_t conn_index = bt_conn_index(conn); |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; |
| |
| LOG_DBG("conn %p attr %p", (void *)conn, attr); |
| |
| if (attr != NULL) { |
| const struct bt_gatt_service_val *prim_service; |
| |
| LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); |
| |
| prim_service = (struct bt_gatt_service_val *)attr->user_data; |
| |
| srv_inst->current_inst = &srv_inst->tbs_insts[srv_inst->inst_cnt++]; |
| srv_inst->current_inst->start_handle = attr->handle + 1; |
| srv_inst->current_inst->end_handle = prim_service->end_handle; |
| |
| if (srv_inst->inst_cnt < ARRAY_SIZE(srv_inst->tbs_insts)) { |
| return BT_GATT_ITER_CONTINUE; |
| } |
| } |
| |
| primary_discover_complete(srv_inst, conn); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| static int primary_discover_tbs(struct bt_conn *conn) |
| { |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; |
| struct bt_gatt_discover_params *params = &srv_inst->discover_params; |
| |
| LOG_DBG("conn %p", (void *)conn); |
| |
| (void)memset(params, 0, sizeof(*params)); |
| params->uuid = tbs_uuid; |
| params->func = primary_discover_tbs_cb; |
| params->type = BT_GATT_DISCOVER_PRIMARY; |
| params->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; |
| params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; |
| |
| return bt_gatt_discover(conn, params); |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| static const struct bt_uuid *gtbs_uuid = BT_UUID_GTBS; |
| |
| static uint8_t primary_discover_gtbs_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| struct bt_gatt_discover_params *params) |
| { |
| const uint8_t conn_index = bt_conn_index(conn); |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; |
| |
| LOG_DBG("conn %p attr %p", (void *)conn, attr); |
| |
| if (attr != NULL) { |
| const struct bt_gatt_service_val *prim_service; |
| |
| LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); |
| |
| prim_service = (struct bt_gatt_service_val *)attr->user_data; |
| |
| srv_inst->current_inst = &srv_inst->gtbs_inst; |
| srv_inst->current_inst->start_handle = attr->handle + 1; |
| srv_inst->current_inst->end_handle = prim_service->end_handle; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| int err; |
| |
| err = primary_discover_tbs(conn); |
| if (err == 0) { |
| return BT_GATT_ITER_STOP; |
| } |
| |
| LOG_DBG("Discover failed (err %d)", err); |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| primary_discover_complete(srv_inst, conn); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| static int primary_discover_gtbs(struct bt_conn *conn) |
| { |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; |
| struct bt_gatt_discover_params *params = &srv_inst->discover_params; |
| |
| LOG_DBG("conn %p", (void *)conn); |
| |
| (void)memset(params, 0, sizeof(*params)); |
| params->uuid = gtbs_uuid; |
| params->func = primary_discover_gtbs_cb; |
| params->type = BT_GATT_DISCOVER_PRIMARY; |
| params->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; |
| params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; |
| |
| return bt_gatt_discover(conn, params); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_GTBS) */ |
| |
| /****************************** PUBLIC API ******************************/ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) |
| int bt_tbs_client_hold_call(struct bt_conn *conn, uint8_t inst_index, |
| uint8_t call_index) |
| { |
| return tbs_client_common_call_control(conn, inst_index, call_index, |
| BT_TBS_CALL_OPCODE_HOLD); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) |
| int bt_tbs_client_accept_call(struct bt_conn *conn, uint8_t inst_index, |
| uint8_t call_index) |
| { |
| return tbs_client_common_call_control(conn, inst_index, call_index, |
| BT_TBS_CALL_OPCODE_ACCEPT); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) |
| int bt_tbs_client_retrieve_call(struct bt_conn *conn, uint8_t inst_index, |
| uint8_t call_index) |
| { |
| return tbs_client_common_call_control(conn, inst_index, call_index, |
| BT_TBS_CALL_OPCODE_RETRIEVE); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) |
| int bt_tbs_client_terminate_call(struct bt_conn *conn, uint8_t inst_index, |
| uint8_t call_index) |
| { |
| return tbs_client_common_call_control(conn, inst_index, call_index, |
| BT_TBS_CALL_OPCODE_TERMINATE); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) |
| int bt_tbs_client_originate_call(struct bt_conn *conn, uint8_t inst_index, |
| const char *uri) |
| { |
| struct bt_tbs_instance *inst; |
| uint8_t write_buf[CONFIG_BT_L2CAP_TX_MTU]; |
| struct bt_tbs_call_cp_originate *originate; |
| size_t uri_len; |
| const size_t max_uri_len = sizeof(write_buf) - sizeof(*originate); |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } else if (!bt_tbs_valid_uri(uri, strlen(uri))) { |
| LOG_DBG("Invalid URI: %s", uri); |
| return -EINVAL; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| /* Check if there are free spots */ |
| if (!free_call_spot(inst)) { |
| LOG_DBG("Cannot originate more calls"); |
| return -ENOMEM; |
| } |
| |
| uri_len = strlen(uri); |
| |
| if (uri_len > max_uri_len) { |
| LOG_DBG("URI len (%zu) longer than maximum writable %zu", uri_len, max_uri_len); |
| return -ENOMEM; |
| } |
| |
| originate = (struct bt_tbs_call_cp_originate *)write_buf; |
| originate->opcode = BT_TBS_CALL_OPCODE_ORIGINATE; |
| (void)memcpy(originate->uri, uri, uri_len); |
| |
| return bt_gatt_write_without_response(conn, inst->call_cp_sub_params.value_handle, |
| originate, |
| sizeof(*originate) + uri_len, |
| false); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) |
| int bt_tbs_client_join_calls(struct bt_conn *conn, uint8_t inst_index, |
| const uint8_t *call_indexes, uint8_t count) |
| { |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| /* Write to call control point */ |
| if (call_indexes && count > 1 && |
| count <= CONFIG_BT_TBS_CLIENT_MAX_CALLS) { |
| struct bt_tbs_instance *inst; |
| struct bt_tbs_call_cp_join *join; |
| uint8_t write_buf[CONFIG_BT_L2CAP_TX_MTU]; |
| const size_t max_call_cnt = sizeof(write_buf) - sizeof(join->opcode); |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->call_cp_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| if (count > max_call_cnt) { |
| LOG_DBG("Call count (%u) larger than maximum writable %zu", count, |
| max_call_cnt); |
| return -ENOMEM; |
| } |
| |
| join = (struct bt_tbs_call_cp_join *)write_buf; |
| |
| join->opcode = BT_TBS_CALL_OPCODE_JOIN; |
| (void)memcpy(join->call_indexes, call_indexes, count); |
| |
| return bt_gatt_write_without_response(conn, |
| inst->call_cp_sub_params.value_handle, |
| join, |
| sizeof(*join) + count, |
| false); |
| } |
| |
| return -EINVAL; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) |
| int bt_tbs_client_set_signal_strength_interval(struct bt_conn *conn, |
| uint8_t inst_index, |
| uint8_t interval) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| /* Populate Outgoing Remote URI */ |
| if (inst->signal_interval_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return bt_gatt_write_without_response(conn, |
| inst->signal_interval_handle, |
| &interval, sizeof(interval), |
| false); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) |
| int bt_tbs_client_read_bearer_provider_name(struct bt_conn *conn, |
| uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->name_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->name_sub_params.value_handle, |
| read_bearer_provider_name_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) |
| int bt_tbs_client_read_bearer_uci(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->bearer_uci_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->bearer_uci_handle, read_bearer_uci_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) |
| int bt_tbs_client_read_technology(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->technology_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->technology_sub_params.value_handle, |
| read_technology_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) |
| int bt_tbs_client_read_uri_list(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->uri_list_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->uri_list_handle, read_uri_list_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) |
| int bt_tbs_client_read_signal_strength(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->signal_strength_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->signal_strength_sub_params.value_handle, |
| read_signal_strength_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) |
| int bt_tbs_client_read_signal_interval(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->signal_interval_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->signal_interval_handle, |
| read_signal_interval_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) |
| int bt_tbs_client_read_current_calls(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->current_calls_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->current_calls_sub_params.value_handle, |
| read_current_calls_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CCID) |
| int bt_tbs_client_read_ccid(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->ccid_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->ccid_handle, read_ccid_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) |
| int bt_tbs_client_read_call_uri(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->in_target_uri_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->in_target_uri_sub_params.value_handle, |
| read_call_uri_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) |
| int bt_tbs_client_read_status_flags(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->status_flags_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->status_flags_sub_params.value_handle, |
| read_status_flags_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ |
| |
| int bt_tbs_client_read_call_state(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->call_state_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->call_state_sub_params.value_handle, |
| read_call_state_cb); |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) |
| int bt_tbs_client_read_optional_opcodes(struct bt_conn *conn, |
| uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->optional_opcodes_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->optional_opcodes_handle, |
| read_optional_opcodes_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) |
| int bt_tbs_client_read_remote_uri(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->incoming_call_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->incoming_call_sub_params.value_handle, |
| read_remote_uri_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) |
| int bt_tbs_client_read_friendly_name(struct bt_conn *conn, uint8_t inst_index) |
| { |
| struct bt_tbs_instance *inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| inst = tbs_inst_by_index(conn, inst_index); |
| if (inst == NULL) { |
| return -EINVAL; |
| } |
| |
| if (inst->friendly_name_sub_params.value_handle == 0) { |
| LOG_DBG("Handle not set"); |
| return -EINVAL; |
| } |
| |
| return tbs_client_gatt_read(conn, inst, inst->friendly_name_sub_params.value_handle, |
| read_friendly_name_cb); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ |
| |
| static bool check_and_set_all_busy(struct bt_tbs_server_inst *srv_inst) |
| { |
| bool all_idle = true; |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| if (atomic_test_and_set_bit(srv_inst->gtbs_inst.flags, BT_TBS_CLIENT_FLAG_BUSY)) { |
| LOG_DBG("GTBS is busy"); |
| |
| return false; |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| size_t num_free; |
| |
| for (num_free = 0U; num_free < ARRAY_SIZE(srv_inst->tbs_insts); num_free++) { |
| struct bt_tbs_instance *tbs_inst = &srv_inst->tbs_insts[num_free]; |
| |
| if (atomic_test_and_set_bit(tbs_inst->flags, BT_TBS_CLIENT_FLAG_BUSY)) { |
| LOG_DBG("inst[%zu] (%p) is busy", num_free, tbs_inst); |
| all_idle = false; |
| |
| break; |
| } |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| |
| /* If any is busy, revert any busy states we've set */ |
| if (!all_idle) { |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| atomic_clear_bit(srv_inst->gtbs_inst.flags, BT_TBS_CLIENT_FLAG_BUSY); |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| for (uint8_t i = 0U; i < num_free; i++) { |
| atomic_clear_bit(srv_inst->tbs_insts[i].flags, BT_TBS_CLIENT_FLAG_BUSY); |
| } |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| } |
| |
| return all_idle; |
| } |
| |
| int bt_tbs_client_discover(struct bt_conn *conn) |
| { |
| uint8_t conn_index; |
| struct bt_tbs_server_inst *srv_inst; |
| |
| if (conn == NULL) { |
| return -ENOTCONN; |
| } |
| |
| conn_index = bt_conn_index(conn); |
| srv_inst = &srv_insts[conn_index]; |
| |
| /* Before we do discovery we ensure that all TBS instances are currently not busy as to not |
| * interfere with any procedures in progress |
| */ |
| if (!check_and_set_all_busy(srv_inst)) { |
| return -EBUSY; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_TBS) |
| (void)memset(srv_inst->tbs_insts, 0, sizeof(srv_inst->tbs_insts)); /* reset data */ |
| srv_inst->inst_cnt = 0; |
| #endif /* CONFIG_BT_TBS_CLIENT_TBS */ |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| (void)memset(&srv_inst->gtbs_inst, 0, sizeof(srv_inst->gtbs_inst)); /* reset data */ |
| return primary_discover_gtbs(conn); |
| #else |
| return primary_discover_tbs(conn); |
| #endif /* CONFIG_BT_TBS_CLIENT_GTBS */ |
| } |
| |
| int bt_tbs_client_register_cb(struct bt_tbs_client_cb *cb) |
| { |
| CHECKIF(cb == NULL) { |
| LOG_DBG("cb is NULL"); |
| |
| return -EINVAL; |
| } |
| |
| if (sys_slist_find(&tbs_client_cbs, &cb->_node, NULL)) { |
| return -EEXIST; |
| } |
| |
| sys_slist_append(&tbs_client_cbs, &cb->_node); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_CCID) |
| static bool tbs_instance_ccid_is_eq(struct bt_tbs_instance *inst, void *user_data) |
| { |
| uint8_t ccid = POINTER_TO_UINT(user_data); |
| |
| return inst->ccid == ccid; |
| } |
| |
| struct bt_tbs_instance *bt_tbs_client_get_by_ccid(const struct bt_conn *conn, |
| uint8_t ccid) |
| { |
| struct bt_tbs_server_inst *server; |
| |
| CHECKIF(conn == NULL) { |
| LOG_DBG("conn was NULL"); |
| return NULL; |
| } |
| |
| server = &srv_insts[bt_conn_index(conn)]; |
| |
| return tbs_instance_find(server, tbs_instance_ccid_is_eq, UINT_TO_POINTER(ccid)); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |