/* Bluetooth TBS - Telephone Bearer Service * * Copyright (c) 2020 Bose Corporation * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audio_internal.h" #include "tbs_internal.h" LOG_MODULE_REGISTER(bt_tbs, CONFIG_BT_TBS_LOG_LEVEL); #define BT_TBS_VALID_STATUS_FLAGS(val) ((val) <= (BIT(0) | BIT(1))) /* A service instance can either be a GTBS or a TBS instance */ struct tbs_inst { /* Attribute values */ char provider_name[CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH]; char uci[BT_TBS_MAX_UCI_SIZE]; uint8_t technology; uint8_t signal_strength; uint8_t signal_strength_interval; uint8_t ccid; uint16_t optional_opcodes; uint16_t status_flags; struct bt_tbs_in_uri incoming_uri; struct bt_tbs_in_uri friendly_name; struct bt_tbs_in_uri in_call; char uri_scheme_list[CONFIG_BT_TBS_MAX_SCHEME_LIST_LENGTH]; struct bt_tbs_terminate_reason terminate_reason; struct bt_tbs_call calls[CONFIG_BT_TBS_MAX_CALLS]; bool notify_current_calls; bool notify_call_states; bool pending_signal_strength_notification; struct k_work_delayable reporting_interval_work; /** Service Attributes */ const struct bt_gatt_attr *attrs; /** Service Attribute count */ size_t attr_count; bool authorization_required; }; static struct tbs_inst svc_insts[CONFIG_BT_TBS_BEARER_COUNT]; static struct tbs_inst gtbs_inst; #define READ_BUF_SIZE \ MAX(BT_ATT_MAX_ATTRIBUTE_LEN, \ (CONFIG_BT_TBS_MAX_CALLS * sizeof(struct bt_tbs_current_call_item) * \ (1U + ARRAY_SIZE(svc_insts)))) NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, READ_BUF_SIZE); /* Used to notify app with held calls in case of join */ static struct bt_tbs_call *held_calls[CONFIG_BT_TBS_MAX_CALLS]; static uint8_t held_calls_cnt; static struct bt_tbs_cb *tbs_cbs; static bool inst_is_registered(const struct tbs_inst *inst) { return inst->attrs != NULL; } static bool inst_is_gtbs(const struct tbs_inst *inst) { if (CONFIG_BT_TBS_BEARER_COUNT > 0) { return inst == >bs_inst; } else { return true; } } static uint8_t inst_index(const struct tbs_inst *inst) { ptrdiff_t index = 0; __ASSERT_NO_MSG(inst); if (inst_is_gtbs(inst)) { return BT_TBS_GTBS_INDEX; } index = inst - svc_insts; __ASSERT(index >= 0 && index < ARRAY_SIZE(svc_insts), "Invalid tbs_inst pointer"); return (uint8_t)index; } static struct tbs_inst *inst_lookup_index(uint8_t index) { struct tbs_inst *inst = NULL; if (index == BT_TBS_GTBS_INDEX) { inst = >bs_inst; } else if (ARRAY_SIZE(svc_insts) > 0U && index < ARRAY_SIZE(svc_insts)) { inst = &svc_insts[index]; } if (inst == NULL || !inst_is_registered(inst)) { return NULL; } return inst; } static struct bt_tbs_call *lookup_call_in_inst(struct tbs_inst *inst, uint8_t call_index) { if (call_index == BT_TBS_FREE_CALL_INDEX) { return NULL; } for (int i = 0; i < ARRAY_SIZE(svc_insts[i].calls); i++) { if (inst->calls[i].index == call_index) { return &inst->calls[i]; } } return NULL; } /** * @brief Finds and returns a call * * @param call_index The ID of the call * @return struct bt_tbs_call* Pointer to the call. NULL if not found */ static struct bt_tbs_call *lookup_call(uint8_t call_index) { struct bt_tbs_call *call; if (call_index == BT_TBS_FREE_CALL_INDEX) { return NULL; } call = lookup_call_in_inst(>bs_inst, call_index); if (call != NULL) { return call; } for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { call = lookup_call_in_inst(&svc_insts[i], call_index); if (call != NULL) { return call; } } return NULL; } static bool inst_check_attr(struct tbs_inst *inst, const struct bt_gatt_attr *attr) { for (size_t j = 0; j < inst->attr_count; j++) { if (&inst->attrs[j] == attr) { return true; } } return false; } static struct tbs_inst *lookup_inst_by_attr(const struct bt_gatt_attr *attr) { if (attr == NULL) { return NULL; } for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) { if (inst_check_attr(&svc_insts[i], attr)) { return &svc_insts[i]; } } if (inst_check_attr(>bs_inst, attr)) { return >bs_inst; } return NULL; } static struct tbs_inst *lookup_inst_by_call_index(uint8_t call_index) { if (call_index == BT_TBS_FREE_CALL_INDEX) { return NULL; } if (lookup_call_in_inst(>bs_inst, call_index) != NULL) { return >bs_inst; } for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { if (lookup_call_in_inst(&svc_insts[i], call_index) != NULL) { return &svc_insts[i]; } } return NULL; } static bool is_authorized(const struct tbs_inst *inst, struct bt_conn *conn) { if (inst->authorization_required) { if (tbs_cbs != NULL && tbs_cbs->authorize != NULL) { return tbs_cbs->authorize(conn); } else { return false; } } return true; } static bool uri_scheme_in_list(const char *uri_scheme, const char *uri_scheme_list) { const size_t scheme_len = strlen(uri_scheme); const size_t scheme_list_len = strlen(uri_scheme_list); const char *uri_scheme_cand = uri_scheme_list; size_t uri_scheme_cand_len; size_t start_idx = 0; for (size_t i = 0; i < scheme_list_len; i++) { if (uri_scheme_list[i] == ',') { uri_scheme_cand_len = i - start_idx; if (uri_scheme_cand_len != scheme_len) { continue; } if (memcmp(uri_scheme, uri_scheme_cand, scheme_len) == 0) { return true; } if (i + 1 < scheme_list_len) { uri_scheme_cand = &uri_scheme_list[i + 1]; } } } return false; } static struct tbs_inst *lookup_inst_by_uri_scheme(const uint8_t *uri, uint8_t uri_len) { char uri_scheme[CONFIG_BT_TBS_MAX_URI_LENGTH] = {0}; if (uri_len == 0) { return NULL; } /* Look for ':' between the first and last char */ for (uint8_t i = 1U; i < uri_len - 1U; i++) { if (uri[i] == ':') { (void)memcpy(uri_scheme, uri, i); break; } } if (uri_scheme[0] == '\0') { /* No URI scheme found */ return NULL; } for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { for (size_t j = 0; j < ARRAY_SIZE(svc_insts[i].calls); j++) { if (uri_scheme_in_list(uri_scheme, svc_insts[i].uri_scheme_list)) { return &svc_insts[i]; } } } /* If not found in any TBS instance, check GTBS */ if (uri_scheme_in_list(uri_scheme, gtbs_inst.uri_scheme_list)) { return >bs_inst; } return NULL; } static void tbs_set_terminate_reason(struct tbs_inst *inst, uint8_t call_index, uint8_t reason) { inst->terminate_reason.call_index = call_index; inst->terminate_reason.reason = reason; LOG_DBG("Index %u: call index 0x%02x, reason %s", inst_index(inst), call_index, bt_tbs_term_reason_str(reason)); bt_gatt_notify_uuid(NULL, BT_UUID_TBS_TERMINATE_REASON, inst->attrs, (void *)&inst->terminate_reason, sizeof(inst->terminate_reason)); } /** * @brief Gets the next free call_index * * For each new call, the call index should be incremented and wrap at 255. * However, the index = 0 is reserved for outgoing calls * * Call indexes are shared among all bearers, so there is always a 1:1 between a call index and a * bearer * * @return uint8_t The next free call index */ static uint8_t next_free_call_index(void) { for (int i = 0; i < CONFIG_BT_TBS_MAX_CALLS; i++) { static uint8_t next_call_index; const struct bt_tbs_call *call; /* For each new call, the call index should be incremented */ next_call_index++; if (next_call_index == BT_TBS_FREE_CALL_INDEX) { /* call_index = 0 reserved for outgoing calls */ next_call_index = 1; } call = lookup_call(next_call_index); if (call == NULL) { return next_call_index; } } LOG_DBG("No more free call spots"); return BT_TBS_FREE_CALL_INDEX; } static struct bt_tbs_call *call_alloc(struct tbs_inst *inst, uint8_t state, const uint8_t *uri, uint16_t uri_len) { struct bt_tbs_call *free_call = NULL; for (size_t i = 0; i < ARRAY_SIZE(inst->calls); i++) { if (inst->calls[i].index == BT_TBS_FREE_CALL_INDEX) { free_call = &inst->calls[i]; break; } } if (free_call == NULL) { return NULL; } __ASSERT_NO_MSG(uri_len < sizeof(free_call->remote_uri)); memset(free_call, 0, sizeof(*free_call)); /* Get the next free call_index */ free_call->index = next_free_call_index(); __ASSERT_NO_MSG(free_call->index != BT_TBS_FREE_CALL_INDEX); free_call->state = state; (void)memcpy(free_call->remote_uri, uri, uri_len); free_call->remote_uri[uri_len] = '\0'; return free_call; } static void call_free(struct bt_tbs_call *call) { call->index = BT_TBS_FREE_CALL_INDEX; } static void net_buf_put_call_states_by_inst(const struct tbs_inst *inst, struct net_buf_simple *buf) { const struct bt_tbs_call *call; const struct bt_tbs_call *calls; size_t call_count; calls = inst->calls; call_count = ARRAY_SIZE(inst->calls); for (size_t i = 0; i < call_count; i++) { call = &calls[i]; if (call->index == BT_TBS_FREE_CALL_INDEX) { continue; } if (buf->len + 3U > buf->size) { LOG_WRN("Not able to store all call states in buffer"); return; } net_buf_simple_add_u8(buf, call->index); net_buf_simple_add_u8(buf, call->state); net_buf_simple_add_u8(buf, call->flags); } } static void net_buf_put_call_states(const struct tbs_inst *inst, struct net_buf_simple *buf) { net_buf_simple_reset(buf); net_buf_put_call_states_by_inst(inst, buf); /* For GTBS we add all the calls the GTBS bearer has itself, as well as all the other * bearers */ if (inst_is_gtbs(inst)) { for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { net_buf_put_call_states_by_inst(&svc_insts[i], buf); } } } static void net_buf_put_current_calls_by_inst(const struct tbs_inst *inst, struct net_buf_simple *buf) { const struct bt_tbs_call *call; const struct bt_tbs_call *calls; size_t call_count; size_t uri_length; size_t item_len; calls = inst->calls; call_count = ARRAY_SIZE(inst->calls); for (size_t i = 0; i < call_count; i++) { call = &calls[i]; if (call->index == BT_TBS_FREE_CALL_INDEX) { continue; } uri_length = strlen(call->remote_uri); item_len = sizeof(call->index) + sizeof(call->state) + sizeof(call->flags) + uri_length; __ASSERT_NO_MSG(item_len <= UINT8_MAX); if (buf->len + sizeof(uint8_t) + item_len > buf->size) { LOG_WRN("Not able to store all calls in buffer"); return; } net_buf_simple_add_u8(buf, (uint8_t)item_len); net_buf_simple_add_u8(buf, call->index); net_buf_simple_add_u8(buf, call->state); net_buf_simple_add_u8(buf, call->flags); net_buf_simple_add_mem(buf, call->remote_uri, uri_length); } } static void net_buf_put_current_calls(const struct tbs_inst *inst, struct net_buf_simple *buf) { net_buf_simple_reset(buf); net_buf_put_current_calls_by_inst(inst, buf); /* For GTBS we add all the calls the GTBS bearer has itself, as well as all the other * bearers */ if (inst_is_gtbs(inst)) { for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { net_buf_put_current_calls_by_inst(&svc_insts[i], buf); } } } static int inst_notify_calls(const struct tbs_inst *inst) { int err; if (inst->notify_call_states) { net_buf_put_call_states(inst, &read_buf); err = bt_gatt_notify_uuid(NULL, BT_UUID_TBS_CALL_STATE, inst->attrs, read_buf.data, read_buf.len); if (err != 0) { return err; } } if (inst->notify_current_calls) { net_buf_put_current_calls(inst, &read_buf); err = bt_gatt_notify_uuid(NULL, BT_UUID_TBS_LIST_CURRENT_CALLS, inst->attrs, read_buf.data, read_buf.len); if (err != 0) { return err; } } return 0; } static int notify_calls(const struct tbs_inst *inst) { int err; if (inst == NULL) { return -EINVAL; } /* Notify TBS */ err = inst_notify_calls(inst); if (err != 0) { return err; } if (!inst_is_gtbs(inst)) { /* If the instance is different than the GTBS notify on the GTBS instance as well */ err = inst_notify_calls(>bs_inst); if (err != 0) { return err; } } return 0; } static ssize_t read_provider_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u, Provider name %s", inst_index(inst), inst->provider_name); return bt_gatt_attr_read(conn, attr, buf, len, offset, inst->provider_name, strlen(inst->provider_name)); } static void provider_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_uci(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u: UCI %s", inst_index(inst), inst->uci); return bt_gatt_attr_read(conn, attr, buf, len, offset, inst->uci, strlen(inst->uci)); } static ssize_t read_technology(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u: Technology 0x%02x", inst_index(inst), inst->technology); return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->technology, sizeof(inst->technology)); } static void technology_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_uri_scheme_list(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); net_buf_simple_reset(&read_buf); net_buf_simple_add_mem(&read_buf, inst->uri_scheme_list, strlen(inst->uri_scheme_list)); if (inst_is_gtbs(inst)) { /* TODO: Make uri schemes unique */ for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { size_t uri_len = strlen(svc_insts[i].uri_scheme_list); if (read_buf.len + uri_len >= read_buf.size) { LOG_WRN("Cannot fit all TBS instances in GTBS " "URI scheme list"); break; } net_buf_simple_add_mem(&read_buf, svc_insts[i].uri_scheme_list, uri_len); } LOG_DBG("GTBS: URI scheme %.*s", read_buf.len, read_buf.data); } else { LOG_DBG("Index %u: URI scheme %.*s", inst_index(inst), read_buf.len, read_buf.data); } return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len); } static void uri_scheme_list_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_signal_strength(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u: Signal strength 0x%02x", inst_index(inst), inst->signal_strength); return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->signal_strength, sizeof(inst->signal_strength)); } static void signal_strength_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_signal_strength_interval(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u: Signal strength interval 0x%02x", inst_index(inst), inst->signal_strength_interval); return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->signal_strength_interval, sizeof(inst->signal_strength_interval)); } static ssize_t write_signal_strength_interval(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); struct net_buf_simple net_buf; uint8_t signal_strength_interval; if (!is_authorized(inst, conn)) { return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); } if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != sizeof(signal_strength_interval)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } net_buf_simple_init_with_data(&net_buf, (void *)buf, len); signal_strength_interval = net_buf_simple_pull_u8(&net_buf); inst->signal_strength_interval = signal_strength_interval; LOG_DBG("Index %u: 0x%02x", inst_index(inst), signal_strength_interval); return len; } static void current_calls_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); inst->notify_current_calls = (value == BT_GATT_CCC_NOTIFY); } } static ssize_t read_current_calls(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u", inst_index(inst)); net_buf_put_current_calls(inst, &read_buf); if (offset == 0) { LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Current calls"); } return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len); } static ssize_t read_ccid(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u: CCID 0x%02x", inst_index(inst), inst->ccid); return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->ccid, sizeof(inst->ccid)); } static ssize_t read_status_flags(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); const uint16_t status_flags_le = sys_cpu_to_le16(inst->optional_opcodes); LOG_DBG("Index %u: status_flags 0x%04x", inst_index(inst), inst->status_flags); return bt_gatt_attr_read(conn, attr, buf, len, offset, &status_flags_le, sizeof(status_flags_le)); } static void status_flags_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_incoming_uri(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); const struct bt_tbs_in_uri *inc_call_target; size_t val_len; inc_call_target = &inst->incoming_uri; LOG_DBG("Index %u: call index 0x%02x, URI %s", inst_index(inst), inc_call_target->call_index, inc_call_target->uri); if (!inc_call_target->call_index) { LOG_DBG("URI not set"); return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); } val_len = sizeof(inc_call_target->call_index) + strlen(inc_call_target->uri); return bt_gatt_attr_read(conn, attr, buf, len, offset, inc_call_target, val_len); } static void incoming_uri_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_call_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); LOG_DBG("Index %u", inst_index(inst)); net_buf_put_call_states(inst, &read_buf); if (offset == 0) { LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Call state"); } return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, read_buf.len); } static void call_state_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); inst->notify_call_states = (value == BT_GATT_CCC_NOTIFY); } } static int notify_ccp(struct bt_conn *conn, const struct bt_gatt_attr *attr, uint8_t call_index, uint8_t opcode, uint8_t status) { const struct bt_tbs_call_cp_notify ccp_not = { .call_index = call_index, .opcode = opcode, .status = status}; LOG_DBG("Notifying CCP: Call index %u, %s opcode and status %s", call_index, bt_tbs_opcode_str(opcode), bt_tbs_status_str(status)); return bt_gatt_notify(conn, attr, &ccp_not, sizeof(ccp_not)); } static void hold_other_calls(struct tbs_inst *inst, uint8_t call_index_cnt, const uint8_t *call_indexes) { held_calls_cnt = 0; for (int i = 0; i < ARRAY_SIZE(inst->calls); i++) { bool hold_call = true; uint8_t call_state; for (int j = 0; j < call_index_cnt; j++) { if (inst->calls[i].index == call_indexes[j]) { hold_call = false; break; } } if (!hold_call) { continue; } call_state = inst->calls[i].state; if (call_state == BT_TBS_CALL_STATE_ACTIVE) { inst->calls[i].state = BT_TBS_CALL_STATE_LOCALLY_HELD; held_calls[held_calls_cnt++] = &inst->calls[i]; } else if (call_state == BT_TBS_CALL_STATE_REMOTELY_HELD) { inst->calls[i].state = BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD; held_calls[held_calls_cnt++] = &inst->calls[i]; } } } static uint8_t accept_call(struct tbs_inst *inst, const struct bt_tbs_call_cp_acc *ccp) { struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } if (call->state == BT_TBS_CALL_STATE_INCOMING) { call->state = BT_TBS_CALL_STATE_ACTIVE; hold_other_calls(inst, 1, &ccp->call_index); return BT_TBS_RESULT_CODE_SUCCESS; } else { return BT_TBS_RESULT_CODE_STATE_MISMATCH; } } static uint8_t terminate_call(struct tbs_inst *inst, const struct bt_tbs_call_cp_term *ccp, uint8_t reason) { struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } call_free(call); tbs_set_terminate_reason(inst, ccp->call_index, reason); if (!inst_is_gtbs(inst)) { /* If the instance is different than the GTBS we set the termination reason and * notify on the GTBS instance as well */ tbs_set_terminate_reason(>bs_inst, ccp->call_index, reason); } return BT_TBS_RESULT_CODE_SUCCESS; } static uint8_t tbs_hold_call(struct tbs_inst *inst, const struct bt_tbs_call_cp_hold *ccp) { struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); if ((inst->optional_opcodes & BT_TBS_FEATURE_HOLD) == 0) { return BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; } if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } if (call->state == BT_TBS_CALL_STATE_ACTIVE) { call->state = BT_TBS_CALL_STATE_LOCALLY_HELD; } else if (call->state == BT_TBS_CALL_STATE_REMOTELY_HELD) { call->state = BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD; } else if (call->state == BT_TBS_CALL_STATE_INCOMING) { call->state = BT_TBS_CALL_STATE_LOCALLY_HELD; } else { return BT_TBS_RESULT_CODE_STATE_MISMATCH; } return BT_TBS_RESULT_CODE_SUCCESS; } static uint8_t retrieve_call(struct tbs_inst *inst, const struct bt_tbs_call_cp_retrieve *ccp) { struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); if ((inst->optional_opcodes & BT_TBS_FEATURE_HOLD) == 0) { return BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; } if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } if (call->state == BT_TBS_CALL_STATE_LOCALLY_HELD) { call->state = BT_TBS_CALL_STATE_ACTIVE; } else if (call->state == BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD) { call->state = BT_TBS_CALL_STATE_REMOTELY_HELD; } else { return BT_TBS_RESULT_CODE_STATE_MISMATCH; } hold_other_calls(inst, 1, &ccp->call_index); return BT_TBS_RESULT_CODE_SUCCESS; } static int originate_call(struct tbs_inst *inst, const struct bt_tbs_call_cp_originate *ccp, uint16_t uri_len, uint8_t *call_index) { struct bt_tbs_call *call; /* Only allow one active outgoing call */ for (int i = 0; i < CONFIG_BT_TBS_MAX_CALLS; i++) { if (inst->calls[i].state == BT_TBS_CALL_STATE_ALERTING) { return BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE; } } if (!bt_tbs_valid_uri(ccp->uri, uri_len)) { return BT_TBS_RESULT_CODE_INVALID_URI; } call = call_alloc(inst, BT_TBS_CALL_STATE_DIALING, ccp->uri, uri_len); if (call == NULL) { return BT_TBS_RESULT_CODE_OUT_OF_RESOURCES; } BT_TBS_CALL_FLAG_SET_OUTGOING(call->flags); hold_other_calls(inst, 1, &call->index); notify_calls(inst); call->state = BT_TBS_CALL_STATE_ALERTING; notify_calls(inst); LOG_DBG("New call with call index %u", call->index); *call_index = call->index; return BT_TBS_RESULT_CODE_SUCCESS; } static uint8_t join_calls(struct tbs_inst *inst, const struct bt_tbs_call_cp_join *ccp, uint16_t call_index_cnt) { struct bt_tbs_call *joined_calls[CONFIG_BT_TBS_MAX_CALLS]; uint8_t call_state; if ((inst->optional_opcodes & BT_TBS_FEATURE_JOIN) == 0) { return BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; } /* Check length */ if (call_index_cnt < 2 || call_index_cnt > CONFIG_BT_TBS_MAX_CALLS) { return BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE; } /* Check for duplicates */ for (int i = 0; i < call_index_cnt; i++) { for (int j = 0; j < i; j++) { if (ccp->call_indexes[i] == ccp->call_indexes[j]) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } } } /* Validate that all calls are in a joinable state */ for (int i = 0; i < call_index_cnt; i++) { joined_calls[i] = lookup_call_in_inst(inst, ccp->call_indexes[i]); if (joined_calls[i] == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } call_state = joined_calls[i]->state; if (call_state == BT_TBS_CALL_STATE_INCOMING) { return BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE; } if (call_state != BT_TBS_CALL_STATE_LOCALLY_HELD && call_state != BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD && call_state != BT_TBS_CALL_STATE_ACTIVE) { return BT_TBS_RESULT_CODE_STATE_MISMATCH; } } /* Join all calls */ for (int i = 0; i < call_index_cnt; i++) { call_state = joined_calls[i]->state; if (call_state == BT_TBS_CALL_STATE_LOCALLY_HELD) { joined_calls[i]->state = BT_TBS_CALL_STATE_ACTIVE; } else if (call_state == BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD) { joined_calls[i]->state = BT_TBS_CALL_STATE_REMOTELY_HELD; } else if (call_state == BT_TBS_CALL_STATE_INCOMING) { joined_calls[i]->state = BT_TBS_CALL_STATE_ACTIVE; } /* else active => Do nothing */ } hold_other_calls(inst, call_index_cnt, ccp->call_indexes); return BT_TBS_RESULT_CODE_SUCCESS; } static void notify_app(struct bt_conn *conn, struct tbs_inst *inst, uint16_t len, const union bt_tbs_call_cp_t *ccp, uint8_t status, uint8_t call_index) { if (tbs_cbs == NULL) { return; } switch (ccp->opcode) { case BT_TBS_CALL_OPCODE_ACCEPT: if (tbs_cbs->accept_call != NULL) { tbs_cbs->accept_call(conn, call_index); } break; case BT_TBS_CALL_OPCODE_TERMINATE: if (tbs_cbs->terminate_call != NULL) { tbs_cbs->terminate_call(conn, call_index, inst->terminate_reason.reason); } break; case BT_TBS_CALL_OPCODE_HOLD: if (tbs_cbs->hold_call != NULL) { tbs_cbs->hold_call(conn, call_index); } break; case BT_TBS_CALL_OPCODE_RETRIEVE: if (tbs_cbs->retrieve_call != NULL) { tbs_cbs->retrieve_call(conn, call_index); } break; case BT_TBS_CALL_OPCODE_ORIGINATE: { char uri[CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; const uint16_t uri_len = len - sizeof(ccp->originate); bool remote_party_alerted = false; struct bt_tbs_call *call; call = lookup_call_in_inst(inst, call_index); if (call == NULL) { LOG_DBG("Could not find call by call index 0x%02x", call_index); break; } (void)memcpy(uri, ccp->originate.uri, uri_len); uri[uri_len] = '\0'; if (tbs_cbs->originate_call != NULL) { remote_party_alerted = tbs_cbs->originate_call(conn, call_index, uri); } if (remote_party_alerted) { call->state = BT_TBS_CALL_STATE_ALERTING; } else { const struct bt_tbs_call_cp_term term = { .call_index = call_index, .opcode = BT_TBS_CALL_OPCODE_TERMINATE}; /* Terminate and remove call */ terminate_call(inst, &term, BT_TBS_REASON_CALL_FAILED); } notify_calls(inst); break; } case BT_TBS_CALL_OPCODE_JOIN: { const uint16_t call_index_cnt = len - sizeof(ccp->join); /* Let the app know about joined calls */ if (tbs_cbs->join_calls != NULL) { tbs_cbs->join_calls(conn, call_index_cnt, ccp->join.call_indexes); } break; } default: break; } /* Let the app know about held calls */ if (held_calls_cnt != 0 && tbs_cbs->hold_call != NULL) { for (int i = 0; i < held_calls_cnt; i++) { tbs_cbs->hold_call(conn, held_calls[i]->index); } } } static ssize_t write_call_cp(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); const union bt_tbs_call_cp_t *ccp = (union bt_tbs_call_cp_t *)buf; struct tbs_inst *tbs = NULL; uint8_t status; uint8_t call_index = 0; const bool is_gtbs = inst_is_gtbs(inst); if (!is_authorized(inst, conn)) { return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); } if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len < sizeof(ccp->opcode)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } LOG_DBG("Index %u: Processing the %s opcode", inst_index(inst), bt_tbs_opcode_str(ccp->opcode)); switch (ccp->opcode) { case BT_TBS_CALL_OPCODE_ACCEPT: if (len != sizeof(ccp->accept)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } call_index = ccp->accept.call_index; if (is_gtbs) { tbs = lookup_inst_by_call_index(call_index); if (tbs == NULL) { status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; break; } } else { tbs = inst; } status = accept_call(tbs, &ccp->accept); break; case BT_TBS_CALL_OPCODE_TERMINATE: if (len != sizeof(ccp->terminate)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } call_index = ccp->terminate.call_index; if (is_gtbs) { tbs = lookup_inst_by_call_index(call_index); if (tbs == NULL) { status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; break; } } else { tbs = inst; } status = terminate_call(tbs, &ccp->terminate, BT_TBS_REASON_CLIENT_TERMINATED); break; case BT_TBS_CALL_OPCODE_HOLD: if (len != sizeof(ccp->hold)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } call_index = ccp->hold.call_index; if (is_gtbs) { tbs = lookup_inst_by_call_index(call_index); if (tbs == NULL) { status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; break; } } else { tbs = inst; } status = tbs_hold_call(tbs, &ccp->hold); break; case BT_TBS_CALL_OPCODE_RETRIEVE: if (len != sizeof(ccp->retrieve)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } call_index = ccp->retrieve.call_index; if (is_gtbs) { tbs = lookup_inst_by_call_index(call_index); if (tbs == NULL) { status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; break; } } else { tbs = inst; } status = retrieve_call(tbs, &ccp->retrieve); break; case BT_TBS_CALL_OPCODE_ORIGINATE: { const uint16_t uri_len = len - sizeof(ccp->originate); if (len < sizeof(ccp->originate) + BT_TBS_MIN_URI_LEN) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } if (is_gtbs) { tbs = lookup_inst_by_uri_scheme(ccp->originate.uri, uri_len); if (tbs == NULL) { status = BT_TBS_RESULT_CODE_INVALID_URI; break; } } else { tbs = inst; } status = originate_call(tbs, &ccp->originate, uri_len, &call_index); break; } case BT_TBS_CALL_OPCODE_JOIN: { const uint16_t call_index_cnt = len - sizeof(ccp->join); if (len < sizeof(ccp->join) + 1) { /* at least 1 call index */ return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } call_index = ccp->join.call_indexes[0]; if (is_gtbs) { tbs = lookup_inst_by_call_index(call_index); if (tbs == NULL) { status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; break; } } else { tbs = inst; } status = join_calls(tbs, &ccp->join, call_index_cnt); break; } default: status = BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; call_index = 0; break; } if (tbs != NULL) { LOG_DBG("Index %u: Processed the %s opcode with status %s for call index %u", inst_index(inst), bt_tbs_opcode_str(ccp->opcode), bt_tbs_status_str(status), call_index); if (status == BT_TBS_RESULT_CODE_SUCCESS) { const struct bt_tbs_call *call = lookup_call(call_index); if (call != NULL) { LOG_DBG("Call is now in the %s state", bt_tbs_state_str(call->state)); } else { LOG_DBG("Call is now terminated"); } } } if (status != BT_TBS_RESULT_CODE_SUCCESS) { call_index = 0; } if (conn != NULL) { notify_ccp(conn, attr, call_index, ccp->opcode, status); } /* else local operation; don't notify */ if (tbs != NULL && status == BT_TBS_RESULT_CODE_SUCCESS) { notify_calls(tbs); notify_app(conn, tbs, len, ccp, status, call_index); } return len; } static void call_cp_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_optional_opcodes(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); const uint16_t optional_opcodes_le = sys_cpu_to_le16(inst->optional_opcodes); LOG_DBG("Index %u: Supported opcodes 0x%02x", inst_index(inst), inst->optional_opcodes); return bt_gatt_attr_read(conn, attr, buf, len, offset, &optional_opcodes_le, sizeof(optional_opcodes_le)); } static void terminate_reason_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_friendly_name(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); const struct bt_tbs_in_uri *friendly_name = &inst->friendly_name; size_t val_len; LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst), friendly_name->call_index, friendly_name->uri); if (friendly_name->call_index == BT_TBS_FREE_CALL_INDEX) { LOG_DBG("URI not set"); return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); } val_len = sizeof(friendly_name->call_index) + strlen(friendly_name->uri); return bt_gatt_attr_read(conn, attr, buf, len, offset, friendly_name, val_len); } static void friendly_name_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } static ssize_t read_incoming_call(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); const struct bt_tbs_in_uri *remote_uri = &inst->in_call; size_t val_len; LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst), remote_uri->call_index, remote_uri->uri); if (remote_uri->call_index == BT_TBS_FREE_CALL_INDEX) { LOG_DBG("URI not set"); return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); } val_len = sizeof(remote_uri->call_index) + strlen(remote_uri->uri); return bt_gatt_attr_read(conn, attr, buf, len, offset, remote_uri, val_len); } static void in_call_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { const struct tbs_inst *inst = lookup_inst_by_attr(attr); if (inst != NULL) { LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); } } #define BT_TBS_CHR_PROVIDER_NAME(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_PROVIDER_NAME, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_provider_name, NULL, inst), \ BT_AUDIO_CCC(provider_name_cfg_changed) #define BT_TBS_CHR_UCI(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_UCI, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_uci, \ NULL, inst) #define BT_TBS_CHR_TECHNOLOGY(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_TECHNOLOGY, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_technology, NULL, inst), \ BT_AUDIO_CCC(technology_cfg_changed) #define BT_TBS_CHR_URI_LIST(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_URI_LIST, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_uri_scheme_list, NULL, inst), \ BT_AUDIO_CCC(uri_scheme_list_cfg_changed) #define BT_TBS_CHR_SIGNAL_STRENGTH(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_SIGNAL_STRENGTH, /* Optional */ \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ read_signal_strength, NULL, inst), \ BT_AUDIO_CCC(signal_strength_cfg_changed) #define BT_TBS_CHR_SIGNAL_INTERVAL(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_SIGNAL_INTERVAL, /* Conditional */ \ BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, \ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ read_signal_strength_interval, write_signal_strength_interval, inst) #define BT_TBS_CHR_CURRENT_CALLS(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_LIST_CURRENT_CALLS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_current_calls, NULL, inst), \ BT_AUDIO_CCC(current_calls_cfg_changed) #define BT_TBS_CHR_CCID(inst) \ BT_AUDIO_CHRC(BT_UUID_CCID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_ccid, NULL, \ inst) #define BT_TBS_CHR_STATUS_FLAGS(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_STATUS_FLAGS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_status_flags, NULL, inst), \ BT_AUDIO_CCC(status_flags_cfg_changed) #define BT_TBS_CHR_INCOMING_URI(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_INCOMING_URI, /* Optional */ \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ read_incoming_uri, NULL, inst), \ BT_AUDIO_CCC(incoming_uri_cfg_changed) #define BT_TBS_CHR_CALL_STATE(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_CALL_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_call_state, NULL, inst), \ BT_AUDIO_CCC(call_state_cfg_changed) #define BT_TBS_CHR_CONTROL_POINT(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_CALL_CONTROL_POINT, \ BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_WRITE_WITHOUT_RESP, \ BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_call_cp, inst), \ BT_AUDIO_CCC(call_cp_cfg_changed) #define BT_TBS_CHR_OPTIONAL_OPCODES(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_OPTIONAL_OPCODES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ read_optional_opcodes, NULL, inst) #define BT_TBS_CHR_TERMINATE_REASON(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_TERMINATE_REASON, BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, NULL, NULL, inst), \ BT_AUDIO_CCC(terminate_reason_cfg_changed) #define BT_TBS_CHR_INCOMING_CALL(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_INCOMING_CALL, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ BT_GATT_PERM_READ_ENCRYPT, read_incoming_call, NULL, inst), \ BT_AUDIO_CCC(in_call_cfg_changed) #define BT_TBS_CHR_FRIENDLY_NAME(inst) \ BT_AUDIO_CHRC(BT_UUID_TBS_FRIENDLY_NAME, /* Optional */ \ BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ read_friendly_name, NULL, inst), \ BT_AUDIO_CCC(friendly_name_cfg_changed) #define BT_TBS_SERVICE_DEFINE(_uuid, _inst) \ BT_GATT_PRIMARY_SERVICE(_uuid), BT_TBS_CHR_PROVIDER_NAME(_inst), BT_TBS_CHR_UCI(_inst), \ BT_TBS_CHR_TECHNOLOGY(_inst), BT_TBS_CHR_URI_LIST(_inst), \ BT_TBS_CHR_SIGNAL_STRENGTH(_inst), BT_TBS_CHR_SIGNAL_INTERVAL(_inst), \ BT_TBS_CHR_CURRENT_CALLS(_inst), BT_TBS_CHR_CCID(_inst), \ BT_TBS_CHR_STATUS_FLAGS(_inst), BT_TBS_CHR_INCOMING_URI(_inst), \ BT_TBS_CHR_CALL_STATE(_inst), BT_TBS_CHR_CONTROL_POINT(_inst), \ BT_TBS_CHR_OPTIONAL_OPCODES(_inst), BT_TBS_CHR_TERMINATE_REASON(_inst), \ BT_TBS_CHR_INCOMING_CALL(_inst), BT_TBS_CHR_FRIENDLY_NAME(_inst) #define BT_TBS_SERVICE_DEFINITION(_inst) \ { \ BT_TBS_SERVICE_DEFINE(BT_UUID_TBS, &(_inst)) \ } static struct bt_gatt_service gtbs_svc = BT_GATT_SERVICE(((struct bt_gatt_attr[]){BT_TBS_SERVICE_DEFINE(BT_UUID_GTBS, >bs_inst)})); BT_GATT_SERVICE_INSTANCE_DEFINE(tbs_service_list, svc_insts, CONFIG_BT_TBS_BEARER_COUNT, BT_TBS_SERVICE_DEFINITION); static void signal_interval_timeout(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); struct tbs_inst *inst = CONTAINER_OF(dwork, struct tbs_inst, reporting_interval_work); if (!inst->pending_signal_strength_notification) { return; } bt_gatt_notify_uuid(NULL, BT_UUID_TBS_SIGNAL_STRENGTH, inst->attrs, &inst->signal_strength, sizeof(inst->signal_strength)); if (inst->signal_strength_interval) { k_work_reschedule(&inst->reporting_interval_work, K_SECONDS(inst->signal_strength_interval)); } inst->pending_signal_strength_notification = false; } static int tbs_inst_init_and_register(struct tbs_inst *inst, struct bt_gatt_service *svc, const struct bt_tbs_register_param *param) { int ret; LOG_DBG("inst %p index 0x%02x", inst, inst_index(inst)); ret = bt_ccid_alloc_value(); if (ret < 0) { LOG_DBG("Could not allocate CCID: %d", ret); return ret; } inst->ccid = (uint8_t)ret; (void)utf8_lcpy(inst->provider_name, param->provider_name, sizeof(inst->provider_name)); (void)utf8_lcpy(inst->uci, param->uci, sizeof(inst->uci)); (void)utf8_lcpy(inst->uri_scheme_list, param->uri_schemes_supported, sizeof(inst->uri_scheme_list)); inst->optional_opcodes = param->supported_features; inst->technology = param->technology; inst->attrs = svc->attrs; inst->attr_count = svc->attr_count; inst->authorization_required = param->authorization_required; k_work_init_delayable(&inst->reporting_interval_work, signal_interval_timeout); ret = bt_gatt_service_register(svc); if (ret != 0) { LOG_DBG("Could not register %sTBS: %d", param->gtbs ? "G" : "", ret); memset(inst, 0, sizeof(*inst)); return ret; } return inst_index(inst); } static int gtbs_service_inst_register(const struct bt_tbs_register_param *param) { return tbs_inst_init_and_register(>bs_inst, >bs_svc, param); } static int tbs_service_inst_register(const struct bt_tbs_register_param *param) { for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { struct tbs_inst *inst = &svc_insts[i]; if (!(inst_is_registered(inst))) { return tbs_inst_init_and_register(inst, &tbs_service_list[i], param); } } return -ENOMEM; } static bool valid_register_param(const struct bt_tbs_register_param *param) { size_t str_len; if (param == NULL) { LOG_DBG("param is NULL"); return false; } if (param->provider_name == NULL) { LOG_DBG("provider_name is NULL"); return false; } str_len = strlen(param->provider_name); if (str_len > CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH) { LOG_DBG("Provider name length (%zu) larger than " "CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH %d", str_len, CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH); return false; } if (param->uci == NULL) { LOG_DBG("uci is NULL"); return false; } if (param->uri_schemes_supported == NULL) { LOG_DBG("uri_schemes_supported is NULL"); return false; } if (!IN_RANGE(param->technology, BT_TBS_TECHNOLOGY_3G, BT_TBS_TECHNOLOGY_WCDMA)) { LOG_DBG("Invalid technology: %u", param->technology); return false; } if (param->supported_features > BT_TBS_FEATURE_ALL) { LOG_DBG("Invalid supported_features: %u", param->supported_features); return false; } if (CONFIG_BT_TBS_BEARER_COUNT == 0 && !param->gtbs) { LOG_DBG("Cannot register TBS when CONFIG_BT_TBS_BEARER_COUNT=0"); return false; } return true; } int bt_tbs_register_bearer(const struct bt_tbs_register_param *param) { int ret; CHECKIF(!valid_register_param(param)) { LOG_DBG("Invalid parameters"); return -EINVAL; } if (param->gtbs && inst_is_registered(>bs_inst)) { LOG_DBG("GTBS already registered"); return -EALREADY; } if (!param->gtbs && !inst_is_registered(>bs_inst)) { LOG_DBG("GTBS not yet registered"); return -EAGAIN; } if (param->gtbs) { ret = gtbs_service_inst_register(param); if (ret < 0) { LOG_DBG("Failed to register GTBS: %d", ret); return -ENOEXEC; } } else if (CONFIG_BT_TBS_BEARER_COUNT > 0) { ret = tbs_service_inst_register(param); if (ret < 0) { LOG_DBG("Failed to register GTBS: %d", ret); if (ret == -ENOMEM) { return -ENOMEM; } return -ENOEXEC; } } /* ret will contain the index of the registered service */ return ret; } int bt_tbs_unregister_bearer(uint8_t bearer_index) { struct tbs_inst *inst = inst_lookup_index(bearer_index); struct bt_gatt_service *svc; struct k_work_sync sync; bool restart_reporting_interval; int err; if (inst == NULL) { LOG_DBG("Could not find inst by index %u", bearer_index); return -EINVAL; } if (!inst_is_registered(inst)) { LOG_DBG("Instance from index %u is not registered", bearer_index); return -EALREADY; } if (inst_is_gtbs(inst)) { for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { struct tbs_inst *tbs = &svc_insts[i]; if (inst_is_registered(tbs)) { LOG_DBG("TBS[%u] is registered, please unregister all TBS first", bearer_index); return -EAGAIN; } } svc = >bs_svc; } else { svc = &tbs_service_list[bearer_index]; } restart_reporting_interval = k_work_cancel_delayable_sync(&inst->reporting_interval_work, &sync); err = bt_gatt_service_unregister(svc); if (err != 0) { LOG_DBG("Failed to unregister service %p: %d", svc, err); if (restart_reporting_interval && inst->signal_strength_interval != 0U) { /* In this unlikely scenario we may report interval later than expected if * the k_work was cancelled right before it was set to trigger. It is not a * big deal and not worth trying to reschedule in a way that it would * trigger at the same time again, as specific timing over GATT is a wishful * dream anyways */ k_work_schedule(&inst->reporting_interval_work, K_SECONDS(inst->signal_strength_interval)); } return -ENOEXEC; } memset(inst, 0, sizeof(*inst)); return 0; } int bt_tbs_accept(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); int status = -EINVAL; const struct bt_tbs_call_cp_acc ccp = {.call_index = call_index, .opcode = BT_TBS_CALL_OPCODE_ACCEPT}; if (inst != NULL) { status = accept_call(inst, &ccp); } if (status == BT_TBS_RESULT_CODE_SUCCESS) { notify_calls(inst); } return status; } int bt_tbs_hold(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); int status = -EINVAL; const struct bt_tbs_call_cp_hold ccp = {.call_index = call_index, .opcode = BT_TBS_CALL_OPCODE_HOLD}; if (inst != NULL) { status = tbs_hold_call(inst, &ccp); } notify_calls(inst); return status; } int bt_tbs_retrieve(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); int status = -EINVAL; const struct bt_tbs_call_cp_retrieve ccp = {.call_index = call_index, .opcode = BT_TBS_CALL_OPCODE_RETRIEVE}; if (inst != NULL) { status = retrieve_call(inst, &ccp); } notify_calls(inst); return status; } int bt_tbs_terminate(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); int status = -EINVAL; const struct bt_tbs_call_cp_term ccp = {.call_index = call_index, .opcode = BT_TBS_CALL_OPCODE_TERMINATE}; if (inst != NULL) { status = terminate_call(inst, &ccp, BT_TBS_REASON_SERVER_ENDED_CALL); } notify_calls(inst); return status; } int bt_tbs_originate(uint8_t bearer_index, char *remote_uri, uint8_t *call_index) { struct tbs_inst *tbs = inst_lookup_index(bearer_index); uint8_t buf[CONFIG_BT_TBS_MAX_URI_LENGTH + sizeof(struct bt_tbs_call_cp_originate)]; struct bt_tbs_call_cp_originate *ccp = (struct bt_tbs_call_cp_originate *)buf; size_t uri_len; if (tbs == NULL) { LOG_DBG("Could not find TBS instance from index %u", bearer_index); return -EINVAL; } else if (!bt_tbs_valid_uri((uint8_t *)remote_uri, strlen(remote_uri))) { LOG_DBG("Invalid URI %s", remote_uri); return -EINVAL; } uri_len = strlen(remote_uri); ccp->opcode = BT_TBS_CALL_OPCODE_ORIGINATE; (void)memcpy(ccp->uri, remote_uri, uri_len); return originate_call(tbs, ccp, uri_len, call_index); } int bt_tbs_join(uint8_t call_index_cnt, uint8_t *call_indexes) { struct tbs_inst *inst; uint8_t buf[CONFIG_BT_TBS_MAX_CALLS + sizeof(struct bt_tbs_call_cp_join)]; struct bt_tbs_call_cp_join *ccp = (struct bt_tbs_call_cp_join *)buf; int status = -EINVAL; if (call_index_cnt != 0 && call_indexes != 0) { inst = lookup_inst_by_call_index(call_indexes[0]); } else { return status; } if (inst != NULL) { ccp->opcode = BT_TBS_CALL_OPCODE_JOIN; (void)memcpy(ccp->call_indexes, call_indexes, MIN(call_index_cnt, CONFIG_BT_TBS_MAX_CALLS)); status = join_calls(inst, ccp, call_index_cnt); } return status; } int bt_tbs_remote_answer(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); struct bt_tbs_call *call; if (inst == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } call = lookup_call_in_inst(inst, call_index); if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } if (call->state == BT_TBS_CALL_STATE_ALERTING) { call->state = BT_TBS_CALL_STATE_ACTIVE; notify_calls(inst); return BT_TBS_RESULT_CODE_SUCCESS; } else { return BT_TBS_RESULT_CODE_STATE_MISMATCH; } } int bt_tbs_remote_hold(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); struct bt_tbs_call *call; uint8_t status; if (inst == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } call = lookup_call_in_inst(inst, call_index); if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } if (call->state == BT_TBS_CALL_STATE_ACTIVE) { call->state = BT_TBS_CALL_STATE_REMOTELY_HELD; status = BT_TBS_RESULT_CODE_SUCCESS; } else if (call->state == BT_TBS_CALL_STATE_LOCALLY_HELD) { call->state = BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD; status = BT_TBS_RESULT_CODE_SUCCESS; } else { status = BT_TBS_RESULT_CODE_STATE_MISMATCH; } if (status == BT_TBS_RESULT_CODE_SUCCESS) { notify_calls(inst); } return status; } int bt_tbs_remote_retrieve(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); struct bt_tbs_call *call; int status; if (inst == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } call = lookup_call_in_inst(inst, call_index); if (call == NULL) { return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; } if (call->state == BT_TBS_CALL_STATE_REMOTELY_HELD) { call->state = BT_TBS_CALL_STATE_ACTIVE; status = BT_TBS_RESULT_CODE_SUCCESS; } else if (call->state == BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD) { call->state = BT_TBS_CALL_STATE_LOCALLY_HELD; status = BT_TBS_RESULT_CODE_SUCCESS; } else { status = BT_TBS_RESULT_CODE_STATE_MISMATCH; } if (status == BT_TBS_RESULT_CODE_SUCCESS) { notify_calls(inst); } return status; } int bt_tbs_remote_terminate(uint8_t call_index) { struct tbs_inst *inst = lookup_inst_by_call_index(call_index); int status = -EINVAL; const struct bt_tbs_call_cp_term ccp = {.call_index = call_index, .opcode = BT_TBS_CALL_OPCODE_TERMINATE}; if (inst != NULL) { status = terminate_call(inst, &ccp, BT_TBS_REASON_REMOTE_ENDED_CALL); } notify_calls(inst); return status; } static void tbs_inst_remote_incoming(struct tbs_inst *inst, const char *to, const char *from, const char *friendly_name, const struct bt_tbs_call *call) { size_t local_uri_ind_len; size_t remote_uri_ind_len; size_t friend_name_ind_len; __ASSERT_NO_MSG(to != NULL); __ASSERT_NO_MSG(from != NULL); local_uri_ind_len = strlen(to) + 1; remote_uri_ind_len = strlen(from) + 1; inst->in_call.call_index = call->index; (void)utf8_lcpy(inst->in_call.uri, from, sizeof(inst->in_call.uri)); inst->incoming_uri.call_index = call->index; (void)utf8_lcpy(inst->incoming_uri.uri, to, sizeof(inst->incoming_uri.uri)); bt_gatt_notify_uuid(NULL, BT_UUID_TBS_INCOMING_URI, inst->attrs, &inst->incoming_uri, local_uri_ind_len); bt_gatt_notify_uuid(NULL, BT_UUID_TBS_INCOMING_CALL, inst->attrs, &inst->in_call, remote_uri_ind_len); if (friendly_name) { inst->friendly_name.call_index = call->index; utf8_lcpy(inst->friendly_name.uri, friendly_name, sizeof(inst->friendly_name.uri)); friend_name_ind_len = strlen(from) + 1; bt_gatt_notify_uuid(NULL, BT_UUID_TBS_FRIENDLY_NAME, inst->attrs, &inst->friendly_name, friend_name_ind_len); } else { inst->friendly_name.call_index = BT_TBS_FREE_CALL_INDEX; bt_gatt_notify_uuid(NULL, BT_UUID_TBS_FRIENDLY_NAME, inst->attrs, NULL, 0); } } int bt_tbs_remote_incoming(uint8_t bearer_index, const char *to, const char *from, const char *friendly_name) { struct tbs_inst *inst = inst_lookup_index(bearer_index); struct bt_tbs_call *call = NULL; if (inst == NULL) { LOG_DBG("Could not find TBS instance from index %u", bearer_index); return -EINVAL; } else if (!bt_tbs_valid_uri((uint8_t *)to, strlen(to))) { LOG_DBG("Invalid \"to\" URI: %s", to); return -EINVAL; } else if (!bt_tbs_valid_uri((uint8_t *)from, strlen(from))) { LOG_DBG("Invalid \"from\" URI: %s", from); return -EINVAL; } call = call_alloc(inst, BT_TBS_CALL_STATE_INCOMING, (uint8_t *)from, strlen(from)); if (call == NULL) { return -ENOMEM; } BT_TBS_CALL_FLAG_SET_INCOMING(call->flags); /* Notify TBS*/ tbs_inst_remote_incoming(inst, to, from, friendly_name, call); if (!inst_is_gtbs(inst)) { /* If the instance is different than the GTBS we set the remote incoming and * notify on the GTBS instance as well */ tbs_inst_remote_incoming(>bs_inst, to, from, friendly_name, call); } notify_calls(inst); LOG_DBG("New call with call index %u", call->index); return call->index; } int bt_tbs_set_bearer_provider_name(uint8_t bearer_index, const char *name) { struct tbs_inst *inst = inst_lookup_index(bearer_index); const size_t len = strlen(name); if (len >= CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH || len == 0) { return -EINVAL; } else if (inst == NULL) { return -EINVAL; } if (strcmp(inst->provider_name, name) == 0) { return 0; } (void)utf8_lcpy(inst->provider_name, name, sizeof(inst->provider_name)); bt_gatt_notify_uuid(NULL, BT_UUID_TBS_PROVIDER_NAME, inst->attrs, inst->provider_name, strlen(inst->provider_name)); return 0; } int bt_tbs_set_bearer_technology(uint8_t bearer_index, uint8_t new_technology) { struct tbs_inst *inst = inst_lookup_index(bearer_index); if (new_technology < BT_TBS_TECHNOLOGY_3G || new_technology > BT_TBS_TECHNOLOGY_WCDMA) { return -EINVAL; } else if (inst == NULL) { return -EINVAL; } if (inst->technology == new_technology) { return 0; } inst->technology = new_technology; bt_gatt_notify_uuid(NULL, BT_UUID_TBS_TECHNOLOGY, inst->attrs, &inst->technology, sizeof(inst->technology)); return 0; } int bt_tbs_set_signal_strength(uint8_t bearer_index, uint8_t new_signal_strength) { struct tbs_inst *inst = inst_lookup_index(bearer_index); uint32_t timer_status; if (new_signal_strength > BT_TBS_SIGNAL_STRENGTH_MAX && new_signal_strength != BT_TBS_SIGNAL_STRENGTH_UNKNOWN) { return -EINVAL; } else if (inst == NULL) { return -EINVAL; } if (inst->signal_strength == new_signal_strength) { return 0; } inst->signal_strength = new_signal_strength; inst->pending_signal_strength_notification = true; timer_status = k_work_delayable_remaining_get(&inst->reporting_interval_work); if (timer_status == 0) { k_work_reschedule(&inst->reporting_interval_work, K_NO_WAIT); } LOG_DBG("Index %u: Reporting signal strength in %d ms", bearer_index, timer_status); return 0; } int bt_tbs_set_status_flags(uint8_t bearer_index, uint16_t status_flags) { struct tbs_inst *inst = inst_lookup_index(bearer_index); if (!BT_TBS_VALID_STATUS_FLAGS(status_flags)) { return -EINVAL; } else if (inst == NULL) { return -EINVAL; } if (inst->status_flags == status_flags) { return 0; } inst->status_flags = status_flags; bt_gatt_notify_uuid(NULL, BT_UUID_TBS_STATUS_FLAGS, inst->attrs, &status_flags, sizeof(status_flags)); return 0; } int bt_tbs_set_uri_scheme_list(uint8_t bearer_index, const char **uri_list, uint8_t uri_count) { char uri_scheme_list[CONFIG_BT_TBS_MAX_SCHEME_LIST_LENGTH]; size_t len = 0; struct tbs_inst *inst; NET_BUF_SIMPLE_DEFINE(uri_scheme_buf, READ_BUF_SIZE); if (bearer_index >= ARRAY_SIZE(svc_insts)) { return -EINVAL; } inst = &svc_insts[bearer_index]; (void)memset(uri_scheme_list, 0, sizeof(uri_scheme_list)); for (int i = 0; i < uri_count; i++) { if (len) { len++; if (len > sizeof(uri_scheme_list) - 1) { return -ENOMEM; } strcat(uri_scheme_list, ","); } len += strlen(uri_list[i]); if (len > sizeof(uri_scheme_list) - 1) { return -ENOMEM; } /* Store list in temp list in case something goes wrong */ strcat(uri_scheme_list, uri_list[i]); } if (strcmp(inst->uri_scheme_list, uri_scheme_list) == 0) { /* identical; don't update or notify */ return 0; } /* Store final result */ (void)utf8_lcpy(inst->uri_scheme_list, uri_scheme_list, sizeof(inst->uri_scheme_list)); LOG_DBG("TBS instance %u uri prefix list is now %s", bearer_index, inst->uri_scheme_list); bt_gatt_notify_uuid(NULL, BT_UUID_TBS_URI_LIST, inst->attrs, &inst->uri_scheme_list, strlen(inst->uri_scheme_list)); if (!inst_is_gtbs(inst)) { /* If the instance is different than the GTBS notify on the GTBS instance as well */ net_buf_simple_add_mem(&uri_scheme_buf, gtbs_inst.uri_scheme_list, strlen(gtbs_inst.uri_scheme_list)); /* TODO: Make uri schemes unique */ for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) { const size_t uri_len = strlen(svc_insts[i].uri_scheme_list); if (uri_scheme_buf.len + uri_len >= uri_scheme_buf.size) { LOG_WRN("Cannot fit all TBS instances in GTBS " "URI scheme list"); break; } net_buf_simple_add_mem(&uri_scheme_buf, svc_insts[i].uri_scheme_list, uri_len); } LOG_DBG("GTBS: URI scheme %.*s", uri_scheme_buf.len, uri_scheme_buf.data); bt_gatt_notify_uuid(NULL, BT_UUID_TBS_URI_LIST, gtbs_inst.attrs, uri_scheme_buf.data, uri_scheme_buf.len); } return 0; } void bt_tbs_register_cb(struct bt_tbs_cb *cbs) { tbs_cbs = cbs; } #if defined(CONFIG_BT_TBS_LOG_LEVEL_DBG) void bt_tbs_dbg_print_calls(void) { for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) { LOG_DBG("Bearer #%u", i); for (int j = 0; j < ARRAY_SIZE(svc_insts[i].calls); j++) { struct bt_tbs_call *call = &svc_insts[i].calls[j]; if (call->index == BT_TBS_FREE_CALL_INDEX) { continue; } LOG_DBG(" Call #%u", call->index); LOG_DBG(" State: %s", bt_tbs_state_str(call->state)); LOG_DBG(" Flags: 0x%02X", call->flags); LOG_DBG(" URI : %s", call->remote_uri); } } } #endif /* defined(CONFIG_BT_TBS_LOG_LEVEL_DBG) */