zephyr/subsys/bluetooth/audio/tbs_client.c
Emil Gydesen 42602e6465 Bluetooth: TBS: Replace busy bool with atomic
Replace the busy boolean flag with an atomic value.
This prevents any race conditions with the implementation.

The discovery procedure is also now properly guarded with it
so that in case that any procedure is currently in progress
for the specific connection, then a new discovery procedure
cannot happen until all other procedures are finished.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2024-09-12 14:48:23 +02:00

2507 lines
74 KiB
C

/* 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 = &current_inst->call_state_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->name_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->technology_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->signal_strength_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->current_calls_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->in_target_uri_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->status_flags_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->call_cp_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->termination_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->friendly_name_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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 = &current_inst->incoming_call_sub_params;
sub_params->value_handle = chrc->value_handle;
sub_params->disc_params = &current_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;
/* 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;
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) */