| /* Bluetooth TBS - Telephone Bearer Service - Client |
| * |
| * Copyright (c) 2020 Bose Corporation |
| * Copyright (c) 2021 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/types.h> |
| #include <zephyr/sys/check.h> |
| |
| #include <zephyr/device.h> |
| #include <zephyr/init.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/buf.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/audio/tbs.h> |
| |
| #include "tbs_internal.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(bt_tbs_client, CONFIG_BT_TBS_CLIENT_LOG_LEVEL); |
| |
| #include "common/bt_str.h" |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_GTBS) |
| #define BT_TBS_INSTANCE_MAX_CNT (CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES + 1) |
| #else |
| #define BT_TBS_INSTANCE_MAX_CNT CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES |
| #endif /* IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS) */ |
| |
| struct bt_tbs_server_inst { |
| struct bt_tbs_instance tbs_insts[BT_TBS_INSTANCE_MAX_CNT]; |
| struct bt_gatt_discover_params discover_params; |
| struct bt_tbs_instance *current_inst; |
| struct bt_tbs_instance *gtbs; |
| uint8_t inst_cnt; |
| bool subscribe_all; |
| }; |
| |
| static const struct bt_tbs_client_cb *tbs_client_cbs; |
| |
| static struct bt_tbs_server_inst srv_insts[CONFIG_BT_MAX_CONN]; |
| static const struct bt_uuid *tbs_uuid = BT_UUID_TBS; |
| static const struct bt_uuid *gtbs_uuid = BT_UUID_GTBS; |
| |
| static void discover_next_instance(struct bt_conn *conn, uint8_t index); |
| |
| 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 (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS)) { |
| /* GTBS can be accessed by BT_TBS_GTBS_INDEX only */ |
| if (index == ARRAY_SIZE(server->tbs_insts) - 1) { |
| return NULL; |
| } |
| |
| if (index == BT_TBS_GTBS_INDEX) { |
| return server->gtbs; |
| } |
| } |
| |
| if (index < server->inst_cnt) { |
| return &server->tbs_insts[index]; |
| } |
| |
| 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 (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS) && inst == server->gtbs) { |
| return BT_TBS_GTBS_INDEX; |
| } |
| |
| index = inst - server->tbs_insts; |
| __ASSERT_NO_MSG(index >= 0 && index < ARRAY_SIZE(server->tbs_insts)); |
| |
| 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 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; |
| |
| __ASSERT(conn, "NULL conn"); |
| |
| conn_index = bt_conn_index(conn); |
| srv_inst = &srv_insts[conn_index]; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(srv_inst->tbs_insts); i++) { |
| if (srv_inst->tbs_insts[i].start_handle <= handle && |
| srv_inst->tbs_insts[i].end_handle >= handle) { |
| return &srv_inst->tbs_insts[i]; |
| } |
| } |
| 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 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++; |
| } |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->current_calls != NULL) { |
| tbs_client_cbs->current_calls(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) |
| { |
| bt_tbs_client_cp_cb cp_cb = NULL; |
| |
| LOG_DBG("Status: %s for the %s opcode for call 0x%02x", bt_tbs_status_str(err), |
| bt_tbs_opcode_str(opcode), call_index); |
| |
| if (tbs_client_cbs == NULL) { |
| return; |
| } |
| |
| switch (opcode) { |
| #if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) |
| case BT_TBS_CALL_OPCODE_ACCEPT: |
| cp_cb = tbs_client_cbs->accept_call; |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) |
| case BT_TBS_CALL_OPCODE_TERMINATE: |
| cp_cb = tbs_client_cbs->terminate_call; |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) |
| case BT_TBS_CALL_OPCODE_HOLD: |
| cp_cb = tbs_client_cbs->hold_call; |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) |
| case BT_TBS_CALL_OPCODE_RETRIEVE: |
| cp_cb = tbs_client_cbs->retrieve_call; |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) |
| case BT_TBS_CALL_OPCODE_ORIGINATE: |
| cp_cb = tbs_client_cbs->originate_call; |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ |
| #if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) |
| case BT_TBS_CALL_OPCODE_JOIN: |
| cp_cb = tbs_client_cbs->join_calls; |
| break; |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ |
| default: |
| break; |
| } |
| |
| if (cp_cb != 0) { |
| cp_cb(conn, err, index, call_index); |
| } |
| } |
| #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_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); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->bearer_provider_name != NULL) { |
| tbs_client_cbs->bearer_provider_name(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_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); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->technology != NULL) { |
| tbs_client_cbs->technology(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_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); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->signal_strength != NULL) { |
| tbs_client_cbs->signal_strength(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_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); |
| if (tbs_client_cbs != NULL && tbs_client_cbs->status_flags != NULL) { |
| tbs_client_cbs->status_flags(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 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); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->call_uri != NULL) { |
| tbs_client_cbs->call_uri(conn, 0, tbs_index(conn, tbs_inst), uri); |
| } |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ |
| |
| 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++; |
| } |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->call_state != NULL) { |
| tbs_client_cbs->call_state(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 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)); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->termination_reason != NULL) { |
| tbs_client_cbs->termination_reason(conn, 0, tbs_index(conn, tbs_inst), |
| reason.call_index, reason.reason); |
| } |
| } |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) |
| 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); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->remote_uri != NULL) { |
| tbs_client_cbs->remote_uri(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_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); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->friendly_name != NULL) { |
| tbs_client_cbs->friendly_name(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 = lookup_inst_by_handle(conn, handle); |
| |
| if (data == NULL) { |
| LOG_DBG("[UNSUBSCRIBED] 0x%04X", params->value_handle); |
| params->value_handle = 0U; |
| if (tbs_inst != NULL) { |
| tbs_inst->subscribe_cnt--; |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| 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; |
| } |
| |
| /* Common function to read tbs_client strings which may require long reads */ |
| static uint8_t handle_string_long_read(struct bt_conn *conn, uint8_t err, |
| struct bt_gatt_read_params *params, |
| const void *data, |
| uint16_t length, |
| bt_tbs_client_read_string_cb cb, |
| bool truncatable) |
| { |
| struct bt_tbs_instance *inst = CONTAINER_OF(params, |
| struct bt_tbs_instance, |
| read_params); |
| uint16_t offset = params->single.offset; |
| uint8_t inst_index = tbs_index(conn, inst); |
| const char *received_string; |
| int tbs_err = err; |
| |
| if ((tbs_err == 0) && (data != NULL) && |
| (net_buf_simple_tailroom(&inst->net_buf) < length)) { |
| LOG_DBG("Read length %u: String buffer full", length); |
| if (truncatable) { |
| /* Use the remaining buffer and continue reading */ |
| LOG_DBG("Truncating string"); |
| length = net_buf_simple_tailroom(&inst->net_buf); |
| } else { |
| tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; |
| } |
| } |
| |
| if (tbs_err != 0) { |
| LOG_DBG("err: %d", tbs_err); |
| if (cb != NULL) { |
| cb(conn, tbs_err, inst_index, NULL); |
| } |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| 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)); |
| |
| net_buf_simple_add_mem(&inst->net_buf, data, length); |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| |
| if (inst->net_buf.len == 0) { |
| received_string = NULL; |
| } else { |
| uint16_t str_length = inst->net_buf.len; |
| |
| /* Ensure there is space for string termination */ |
| if (net_buf_simple_tailroom(&inst->net_buf) < 1) { |
| LOG_DBG("Truncating string"); |
| if (truncatable) { |
| /* Truncate */ |
| str_length--; |
| } else { |
| tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; |
| } |
| } |
| |
| if (tbs_err == 0) { |
| char *str_data; |
| |
| /* Get a reference to the string buffer */ |
| str_data = net_buf_simple_pull_mem(&inst->net_buf, |
| inst->net_buf.len); |
| |
| /* All strings are UTF-8, truncate properly if needed */ |
| str_data[str_length] = '\0'; |
| received_string = utf8_trunc(str_data); |
| |
| /* The string might have been truncated */ |
| if (strlen(received_string) < str_length) { |
| LOG_DBG("Truncating string"); |
| if (!truncatable) { |
| tbs_err = |
| BT_ATT_ERR_INSUFFICIENT_RESOURCES; |
| } |
| } |
| |
| LOG_DBG("%s", received_string); |
| } |
| } |
| |
| if (tbs_err) { |
| received_string = NULL; |
| } |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| if (cb != NULL) { |
| cb(conn, tbs_err, inst_index, received_string); |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| #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) |
| { |
| bt_tbs_client_read_string_cb cb = NULL; |
| |
| LOG_DBG("Read bearer provider name"); |
| |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->bearer_provider_name != NULL) { |
| cb = tbs_client_cbs->bearer_provider_name; |
| } |
| |
| return handle_string_long_read(conn, err, params, data, |
| length, cb, true); |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_BEARER_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) |
| { |
| bt_tbs_client_read_string_cb cb = NULL; |
| |
| LOG_DBG("Read bearer UCI"); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->bearer_uci != NULL) { |
| cb = tbs_client_cbs->bearer_uci; |
| } |
| |
| /* The specification does not indicate truncation as an option, so |
| * fail if insufficient buffer space. |
| */ |
| return handle_string_long_read(conn, err, params, data, |
| length, cb, false); |
| } |
| #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; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->technology != NULL) { |
| tbs_client_cbs->technology(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 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) |
| { |
| bt_tbs_client_read_string_cb cb = NULL; |
| |
| LOG_DBG("Read bearer UCI"); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->uri_list != NULL) { |
| cb = tbs_client_cbs->uri_list; |
| } |
| |
| return handle_string_long_read(conn, err, params, data, |
| length, cb, false); |
| } |
| #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; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->signal_strength != NULL) { |
| tbs_client_cbs->signal_strength(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 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; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (tbs_client_cbs && tbs_client_cbs->signal_interval) { |
| tbs_client_cbs->signal_interval(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); |
| (void)memset(params, 0, sizeof(*params)); |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->current_calls != NULL) { |
| tbs_client_cbs->current_calls(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; |
| } |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| if (inst->net_buf.len == 0) { |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->current_calls != NULL) { |
| tbs_client_cbs->current_calls(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 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; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->ccid != NULL) { |
| tbs_client_cbs->ccid(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; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->status_flags != NULL) { |
| tbs_client_cbs->status_flags(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) |
| { |
| bt_tbs_client_read_string_cb cb = NULL; |
| |
| LOG_DBG("Read incoming call target bearer URI"); |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->call_uri != NULL) { |
| cb = tbs_client_cbs->call_uri; |
| } |
| |
| return handle_string_long_read(conn, err, params, data, |
| length, cb, false); |
| } |
| #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); |
| (void)memset(params, 0, sizeof(*params)); |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->call_state != NULL) { |
| tbs_client_cbs->call_state(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; |
| } |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| if (inst->net_buf.len == 0) { |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->call_state != NULL) { |
| tbs_client_cbs->call_state(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++; |
| } |
| |
| if (tbs_client_cbs != NULL && tbs_client_cbs->call_state != NULL) { |
| tbs_client_cbs->call_state(conn, tbs_err, inst_index, cnt, call_states); |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_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; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->optional_opcodes != NULL) { |
| tbs_client_cbs->optional_opcodes(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) |
| { |
| bt_tbs_client_read_string_cb cb = NULL; |
| |
| LOG_DBG("Read incoming call URI"); |
| |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->remote_uri != NULL) { |
| cb = tbs_client_cbs->remote_uri; |
| } |
| |
| return handle_string_long_read(conn, err, params, data, |
| length, cb, false); |
| } |
| #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) |
| { |
| bt_tbs_client_read_string_cb cb = NULL; |
| |
| LOG_DBG("Read incoming call target bearer URI"); |
| |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->friendly_name != NULL) { |
| cb = tbs_client_cbs->friendly_name; |
| } |
| |
| return handle_string_long_read(conn, err, params, data, |
| length, cb, true); |
| } |
| #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); |
| struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; |
| uint8_t inst_index = tbs_index(conn, inst); |
| int cb_err = err; |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| 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; |
| } |
| } |
| |
| inst->busy = false; |
| |
| if (cb_err != 0) { |
| tbs_client_cbs->discover(conn, cb_err, 0U, false); |
| } else { |
| if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS) && inst == srv_inst->gtbs) { |
| LOG_DBG("Setup complete GTBS"); |
| |
| inst_index = 0; |
| } else { |
| inst_index++; |
| |
| LOG_DBG("Setup complete for %u / %u TBS", inst_index, srv_inst->inst_cnt); |
| } |
| |
| (void)memset(params, 0, sizeof(*params)); |
| |
| if (inst_index < srv_inst->inst_cnt) { |
| discover_next_instance(conn, inst_index); |
| } else { |
| srv_inst->current_inst = NULL; |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->discover != NULL) { |
| tbs_client_cbs->discover(conn, 0, |
| srv_inst->inst_cnt, |
| srv_inst->gtbs != NULL); |
| } |
| } |
| } |
| |
| 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; |
| |
| inst->read_params.func = disc_read_ccid_cb; |
| inst->read_params.handle_count = 1U; |
| inst->read_params.single.handle = inst->ccid_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| srv_inst->current_inst = NULL; |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->discover != NULL) { |
| tbs_client_cbs->discover(conn, err, 0U, false); |
| } |
| } |
| } |
| #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); |
| #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 (srv_insts[conn_index].subscribe_all && 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; |
| |
| /* Setting ccc_handle = will use auto discovery feature */ |
| sub_params->ccc_handle = 0; |
| sub_params->end_handle = current_inst->end_handle; |
| sub_params->notify = notify_handler; |
| err = bt_gatt_subscribe(conn, sub_params); |
| if (err != 0) { |
| LOG_DBG("Could not subscribe to " |
| "characterstic at handle 0x%04X" |
| "(%d)", |
| sub_params->value_handle, err); |
| } else { |
| LOG_DBG("Subscribed to characterstic at " |
| "handle 0x%04X", |
| sub_params->value_handle); |
| } |
| } |
| } |
| } |
| |
| return BT_GATT_ITER_CONTINUE; |
| } |
| |
| static void discover_next_instance(struct bt_conn *conn, uint8_t index) |
| { |
| 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 = tbs_inst_by_index(conn, index); |
| |
| (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) { |
| LOG_DBG("Discover failed (err %d)", err); |
| srv_inst->current_inst = NULL; |
| if (tbs_client_cbs != NULL && |
| tbs_client_cbs->discover != NULL) { |
| tbs_client_cbs->discover(conn, err, srv_inst->inst_cnt, |
| srv_inst->gtbs != NULL); |
| } |
| } |
| } |
| |
| 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)", server->inst_cnt, |
| server->gtbs != NULL ? "" : " not"); |
| } else { |
| LOG_DBG("Discover complete, found %u instances", server->inst_cnt); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS) && server->gtbs != NULL) { |
| discover_next_instance(conn, BT_TBS_GTBS_INDEX); |
| } else if (server->inst_cnt > 0) { |
| discover_next_instance(conn, 0); |
| } else { |
| server->current_inst = NULL; |
| if (tbs_client_cbs != NULL && tbs_client_cbs->discover != NULL) { |
| tbs_client_cbs->discover(conn, 0, 0, false); |
| } |
| } |
| } |
| |
| /** |
| * @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 primary_discover_tbs(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]; |
| |
| 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 < CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES) { |
| return BT_GATT_ITER_CONTINUE; |
| } |
| } |
| |
| primary_discover_complete(srv_inst, conn); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| static uint8_t primary_discover_gtbs(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]; |
| |
| 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; |
| |
| /* GTBS is placed as the "last" instance */ |
| srv_inst->gtbs = &srv_inst->tbs_insts[ARRAY_SIZE(srv_inst->tbs_insts) - 1]; |
| srv_inst->gtbs->start_handle = attr->handle + 1; |
| srv_inst->gtbs->end_handle = prim_service->end_handle; |
| |
| srv_inst->current_inst = srv_inst->gtbs; |
| } |
| |
| if (CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES > 0) { |
| int err; |
| |
| params->uuid = tbs_uuid; |
| params->func = primary_discover_tbs; |
| params->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; |
| |
| err = bt_gatt_discover(conn, params); |
| if (err == 0) { |
| return BT_GATT_ITER_STOP; |
| } |
| |
| LOG_DBG("Discover failed (err %d)", err); |
| } |
| |
| primary_discover_complete(srv_inst, conn); |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| 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); |
| } |
| |
| /****************************** 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)) { |
| 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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_bearer_provider_name_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->name_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_bearer_uci_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->bearer_uci_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| inst->read_params.func = read_technology_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->technology_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_uri_list_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->uri_list_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| inst->read_params.func = read_signal_strength_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->signal_strength_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| inst->read_params.func = read_signal_interval_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->signal_interval_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_current_calls_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->current_calls_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| inst->read_params.func = read_ccid_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->ccid_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_call_uri_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->in_target_uri_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| inst->read_params.func = read_status_flags_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->status_flags_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ |
| |
| int bt_tbs_client_read_call_state(struct bt_conn *conn, uint8_t inst_index) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_call_state_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->call_state_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) |
| int bt_tbs_client_read_optional_opcodes(struct bt_conn *conn, |
| uint8_t inst_index) |
| { |
| int err; |
| 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; |
| } |
| |
| inst->read_params.func = read_optional_opcodes_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->optional_opcodes_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_remote_uri_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->incoming_call_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #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) |
| { |
| int err; |
| 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; |
| } |
| |
| /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ |
| initialize_net_buf_read_buffer(inst); |
| inst->read_params.func = read_friendly_name_cb; |
| inst->read_params.handle_count = 1; |
| inst->read_params.single.handle = inst->friendly_name_sub_params.value_handle; |
| inst->read_params.single.offset = 0U; |
| |
| err = bt_gatt_read(conn, &inst->read_params); |
| if (err != 0) { |
| (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); |
| } |
| |
| return err; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ |
| |
| int bt_tbs_client_discover(struct bt_conn *conn, bool subscribe) |
| { |
| 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]; |
| |
| if (srv_inst->current_inst) { |
| return -EBUSY; |
| } |
| |
| (void)memset(srv_inst->tbs_insts, 0, sizeof(srv_inst->tbs_insts)); /* reset data */ |
| srv_inst->inst_cnt = 0; |
| srv_inst->gtbs = NULL; |
| /* Discover TBS on peer, setup handles and notify/indicate */ |
| srv_inst->subscribe_all = subscribe; |
| (void)memset(&srv_inst->discover_params, 0, sizeof(srv_inst->discover_params)); |
| if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS)) { |
| LOG_DBG("Discovering GTBS"); |
| srv_inst->discover_params.uuid = gtbs_uuid; |
| srv_inst->discover_params.func = primary_discover_gtbs; |
| } else { |
| srv_inst->discover_params.uuid = tbs_uuid; |
| srv_inst->discover_params.func = primary_discover_tbs; |
| } |
| srv_inst->discover_params.type = BT_GATT_DISCOVER_PRIMARY; |
| srv_inst->discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; |
| srv_inst->discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; |
| |
| return bt_gatt_discover(conn, &srv_inst->discover_params); |
| } |
| |
| void bt_tbs_client_register_cb(const struct bt_tbs_client_cb *cbs) |
| { |
| tbs_client_cbs = cbs; |
| } |
| |
| #if defined(CONFIG_BT_TBS_CLIENT_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)]; |
| |
| for (size_t i = 0; i < ARRAY_SIZE(server->tbs_insts); i++) { |
| if (server->tbs_insts[i].ccid == ccid) { |
| return &server->tbs_insts[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| #endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ |