diff --git a/include/bluetooth/audio/tbs.h b/include/bluetooth/audio/tbs.h new file mode 100644 index 00000000000..02f5d5183f1 --- /dev/null +++ b/include/bluetooth/audio/tbs.h @@ -0,0 +1,331 @@ +/** @file + * @brief Public APIs for Bluetooth Telephone Bearer Service. + * + * Copyright (c) 2020 Bose Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_TBS_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_TBS_H_ + +#include +#include + +/* Call States */ +#define BT_TBS_CALL_STATE_INCOMING 0x00 +#define BT_TBS_CALL_STATE_DIALING 0x01 +#define BT_TBS_CALL_STATE_ALERTING 0x02 +#define BT_TBS_CALL_STATE_ACTIVE 0x03 +#define BT_TBS_CALL_STATE_LOCALLY_HELD 0x04 +#define BT_TBS_CALL_STATE_REMOTELY_HELD 0x05 +#define BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD 0x06 + +/* Terminate Reason */ +#define BT_TBS_REASON_BAD_REMOTE_URI 0x00 +#define BT_TBS_REASON_CALL_FAILED 0x01 +#define BT_TBS_REASON_REMOTE_ENDED_CALL 0x02 +#define BT_TBS_REASON_SERVER_ENDED_CALL 0x03 +#define BT_TBS_REASON_LINE_BUSY 0x04 +#define BT_TBS_REASON_NETWORK_CONGESTED 0x05 +#define BT_TBS_REASON_CLIENT_TERMINATED 0x06 +#define BT_TBS_REASON_UNSPECIFIED 0x07 + +/* Application error codes */ +#define BT_TBS_RESULT_CODE_SUCCESS 0x00 +#define BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED 0x01 +#define BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE 0x02 +#define BT_TBS_RESULT_CODE_INVALID_CALL_INDEX 0x03 +#define BT_TBS_RESULT_CODE_STATE_MISMATCH 0x04 +#define BT_TBS_RESULT_CODE_OUT_OF_RESOURCES 0x05 +#define BT_TBS_RESULT_CODE_INVALID_URI 0x06 + +#define BT_TBS_FEATURE_HOLD BIT(0) +#define BT_TBS_FEATURE_JOIN BIT(1) + +#define BT_TBS_CALL_FLAG_SET_INCOMING(flag) (flag &= ~BIT(0)) +#define BT_TBS_CALL_FLAG_SET_OUTGOING(flag) (flag |= BIT(0)) + +#define BT_TBS_SIGNAL_STRENGTH_NO_SERVICE 0 +#define BT_TBS_SIGNAL_STRENGTH_MAX 100 +#define BT_TBS_SIGNAL_STRENGTH_UNKNOWN 255 + +/* Bearer Technology */ +#define BT_TBS_TECHNOLOGY_3G 0x01 +#define BT_TBS_TECHNOLOGY_4G 0x02 +#define BT_TBS_TECHNOLOGY_LTE 0x03 +#define BT_TBS_TECHNOLOGY_WIFI 0x04 +#define BT_TBS_TECHNOLOGY_5G 0x05 +#define BT_TBS_TECHNOLOGY_GSM 0x06 +#define BT_TBS_TECHNOLOGY_CDMA 0x07 +#define BT_TBS_TECHNOLOGY_2G 0x08 +#define BT_TBS_TECHNOLOGY_WCDMA 0x09 +#define BT_TBS_TECHNOLOGY_IP 0x0a + +/** + * @brief The GTBS index denotes whenever a callback is from a + * Generic Telephone Bearer Service (GTBS) instance, or + * whenever the client should perform on action on the GTBS instance of the + * server, rather than any of the specific Telephone Bearer Service instances. + */ +#define BT_TBS_GTBS_INDEX 0xFF + +/** + * @brief Callback function for client originating a call. + * + * @param conn The connection used. + * @param call_index The call index. + * @param uri The URI. The value may change, so should be + * copied if persistence is wanted. + * + * @return true if the call request was accepted and remote party is alerted. + */ +typedef bool (*bt_tbs_originate_call_cb)(struct bt_conn *conn, + uint8_t call_index, + const char *uri); + +/** + * @brief Callback function for client terminating a call. + * + * The call may be either terminated by the client or the server. + * + * @param conn The connection used. + * @param call_index The call index. + * @param reason The termination BT_TBS_REASON_* reason. + */ +typedef void (*bt_tbs_terminate_call_cb)(struct bt_conn *conn, + uint8_t call_index, + uint8_t reason); + +/** + * @brief Callback function for client joining calls. + * + * @param conn The connection used. + * @param call_index_count The number of call indexes to join. + * @param call_indexes The call indexes. + */ +typedef void (*bt_tbs_join_calls_cb)(struct bt_conn *conn, + uint8_t call_index_count, + const uint8_t *call_indexes); + +/** + * @brief Callback function for client request call state change + * + * @param conn The connection used. + * @param call_index The call index. + */ +typedef void (*bt_tbs_call_change_cb)(struct bt_conn *conn, + uint8_t call_index); + +/** + * @brief Callback function for authorizing a client. + * + * Only used if BT_TBS_AUTHORIZATION is enabled. + * + * @param conn The connection used. + * + * @return true if authorized, false otherwise + */ +typedef bool (*bt_tbs_authorize_cb)(struct bt_conn *conn); + +struct bt_tbs_cb { + bt_tbs_originate_call_cb originate_call; + bt_tbs_terminate_call_cb terminate_call; + bt_tbs_call_change_cb hold_call; + bt_tbs_call_change_cb accept_call; + bt_tbs_call_change_cb retrieve_call; + bt_tbs_join_calls_cb join_calls; + bt_tbs_authorize_cb authorize; +}; + +/** + * @brief Accept an alerting call. + * + * @param call_index The index of the call that will be accepted. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_accept(uint8_t call_index); + +/** + * @brief Hold a call. + * + * @param call_index The index of the call that will be held. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_hold(uint8_t call_index); + +/** + * @brief Retrieve a call. + * + * @param call_index The index of the call that will be retrieved. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_retrieve(uint8_t call_index); + +/** + * @brief Terminate a call. + * + * @param call_index The index of the call that will be terminated. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_terminate(uint8_t call_index); + +/** + * @brief Originate a call + * + * @param[in] bearer_index The index of the Telephone Bearer. + * @param[in] uri The remote URI. + * @param[out] call_index Pointer to a value where the new call_index will be + * stored. + * + * @return int A call index on success (positive value), + * errno value on fail. + */ +int bt_tbs_originate(uint8_t bearer_index, char *uri, uint8_t *call_index); + +/** + * @brief Join calls + * + * @param call_index_cnt The number of call indexes to join + * @param call_indexes Array of call indexes to join. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_join(uint8_t call_index_cnt, uint8_t *call_indexes); + +/** + * @brief Notify the server that the remote party answered the call. + * + * @param call_index The index of the call that was answered. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_remote_answer(uint8_t call_index); + +/** + * @brief Notify the server that the remote party held the call. + * + * @param call_index The index of the call that was held. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_remote_hold(uint8_t call_index); + +/** + * @brief Notify the server that the remote party retrieved the call. + * + * @param call_index The index of the call that was retrieved. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_remote_retrieve(uint8_t call_index); + +/** + * @brief Notify the server that the remote party terminated the call. + * + * @param call_index The index of the call that was terminated. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_remote_terminate(uint8_t call_index); + +/** + * @brief Notify the server of an incoming call. + * + * @param bearer_index The index of the Telephone Bearer. + * @param to The URI that is receiving the call. + * @param from The URI of the remote caller. + * @param friendly_name The friendly name of the remote caller. + * + * @return int New call index if positive or 0, + * errno value if negative. + */ +int bt_tbs_remote_incoming(uint8_t bearer_index, const char *to, + const char *from, const char *friendly_name); + +/** + * @brief Set a new bearer provider. + * + * @param bearer_index The index of the Telephone Bearer or BT_TBS_GTBS_INDEX + * for GTBS. + * @param name The new bearer provider name. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_set_bearer_provider_name(uint8_t bearer_index, const char *name); + +/** + * @brief Set a new bearer technology. + * + * @param bearer_index The index of the Telephone Bearer or BT_TBS_GTBS_INDEX + * for GTBS. + * @param new_technology The new bearer technology. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_set_bearer_technology(uint8_t bearer_index, uint8_t new_technology); + +/** + * @brief Update the signal strength reported by the server. + * + * @param bearer_index The index of the Telephone Bearer or + * BT_TBS_GTBS_INDEX for GTBS. + * @param new_signal_strength The new signal strength. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_set_signal_strength(uint8_t bearer_index, + uint8_t new_signal_strength); + +/** + * @brief Sets the feature and status value. + * + * @param bearer_index The index of the Telephone Bearer or BT_TBS_GTBS_INDEX + * for GTBS. + * @param status_flags The new feature and status value. + * + * @return int BT_TBS_RESULT_CODE_* if positive or 0, + * errno value if negative. + */ +int bt_tbs_set_status_flags(uint8_t bearer_index, uint16_t status_flags); + +/** @brief Sets the URI scheme list of a bearer. + * + * @param bearer_index The index of the Telephone Bearer. + * @param uri_list List of URI prefixes (e.g. {"skype", "tel"}). + * @param uri_count Number of URI prefixies in @p uri_list. + * + * @return BT_TBS_RESULT_CODE_* if positive or 0, errno value if negative. + */ +int bt_tbs_set_uri_scheme_list(uint8_t bearer_index, const char **uri_list, + uint8_t uri_count); +/** + * @brief Register the callbacks for TBS. + * + * @param cbs Pointer to the callback structure. + */ +void bt_tbs_register_cb(struct bt_tbs_cb *cbs); + +#if defined(CONFIG_BT_DEBUG_TBS) +/** @brief Prints all calls of all services to the debug log */ +void bt_tbs_dbg_print_calls(void); +#endif /* defined(CONFIG_BT_DEBUG_TBS) */ + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_TBS_H_ */ diff --git a/include/bluetooth/uuid.h b/include/bluetooth/uuid.h index deecf92f23c..39f974d3ad8 100644 --- a/include/bluetooth/uuid.h +++ b/include/bluetooth/uuid.h @@ -473,6 +473,24 @@ struct bt_uuid_128 { */ #define BT_UUID_GMCS \ BT_UUID_DECLARE_16(BT_UUID_GMCS_VAL) +/** @def BT_UUID_TBS_VAL + * @brief Telephone Bearer Service value + */ +#define BT_UUID_TBS_VAL 0x184B +/** @def BT_UUID_TBS + * @brief Telephone Bearer Service + */ +#define BT_UUID_TBS \ + BT_UUID_DECLARE_16(BT_UUID_TBS_VAL) +/** @def BT_UUID_GTBS_VAL + * @brief Generic Telephone Bearer Service value + */ +#define BT_UUID_GTBS_VAL 0x184C +/** @def BT_UUID_GTBS + * @brief Generic Telephone Bearer Service + */ +#define BT_UUID_GTBS \ + BT_UUID_DECLARE_16(BT_UUID_GTBS_VAL) /** @def BT_UUID_MICS_VAL * @brief Microphone Input Control Service value */ @@ -1826,6 +1844,69 @@ struct bt_uuid_128 { */ #define BT_UUID_OTS_TYPE_GROUP \ BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_GROUP_VAL) +/** @def BT_UUID_TBS_PROVIDER_NAME_VAL + * @brief Bearer Provider Name value + */ +#define BT_UUID_TBS_PROVIDER_NAME_VAL 0x2BB3 +/** @def BT_UUID_TBS_PROVIDER_NAME + * @brief Bearer Provider Name + */ +#define BT_UUID_TBS_PROVIDER_NAME \ + BT_UUID_DECLARE_16(BT_UUID_TBS_PROVIDER_NAME_VAL) +/** @def BT_UUID_TBS_UCI_VAL + * @brief Bearer UCI value + */ +#define BT_UUID_TBS_UCI_VAL 0x2BB4 +/** @def BT_UUID_TBS_UCI + * @brief Bearer UCI + */ +#define BT_UUID_TBS_UCI \ + BT_UUID_DECLARE_16(BT_UUID_TBS_UCI_VAL) +/** @def BT_UUID_TBS_TECHNOLOGY_VAL + * @brief Bearer Technology value + */ +#define BT_UUID_TBS_TECHNOLOGY_VAL 0x2BB5 +/** @def BT_UUID_TBS_TECHNOLOGY + * @brief Bearer Technology + */ +#define BT_UUID_TBS_TECHNOLOGY \ + BT_UUID_DECLARE_16(BT_UUID_TBS_TECHNOLOGY_VAL) +/** @def BT_UUID_TBS_URI_LIST_VAL + * @brief Bearer URI Prefixes Supported List value + */ +#define BT_UUID_TBS_URI_LIST_VAL 0x2BB6 +/** @def BT_UUID_TBS_URI_LIST + * @brief Bearer URI Prefixes Supported List + */ +#define BT_UUID_TBS_URI_LIST \ + BT_UUID_DECLARE_16(BT_UUID_TBS_URI_LIST_VAL) +/** @def BT_UUID_TBS_SIGNAL_STRENGTH_VAL + * @brief Bearer Signal Strength value + */ +#define BT_UUID_TBS_SIGNAL_STRENGTH_VAL 0x2BB7 +/** @def BT_UUID_TBS_SIGNAL_STRENGTH + * @brief Bearer Signal Strength + */ +#define BT_UUID_TBS_SIGNAL_STRENGTH \ + BT_UUID_DECLARE_16(BT_UUID_TBS_SIGNAL_STRENGTH_VAL) +/** @def BT_UUID_TBS_SIGNAL_INTERVAL_VAL + * @brief Bearer Signal Strength Reporting Interval value + */ +#define BT_UUID_TBS_SIGNAL_INTERVAL_VAL 0x2BB8 +/** @def BT_UUID_TBS_SIGNAL_INTERVAL + * @brief Bearer Signal Strength Reporting Interval + */ +#define BT_UUID_TBS_SIGNAL_INTERVAL \ + BT_UUID_DECLARE_16(BT_UUID_TBS_SIGNAL_INTERVAL_VAL) +/** @def BT_UUID_TBS_LIST_CURRENT_CALLS_VAL + * @brief Bearer List Current Calls value + */ +#define BT_UUID_TBS_LIST_CURRENT_CALLS_VAL 0x2BB9 +/** @def BT_UUID_TBS_LIST_CURRENT_CALLS + * @brief Bearer List Current Calls + */ +#define BT_UUID_TBS_LIST_CURRENT_CALLS \ + BT_UUID_DECLARE_16(BT_UUID_TBS_LIST_CURRENT_CALLS_VAL) /** @def BT_UUID_CCID_VAL * @brief Content Control ID value */ @@ -1835,6 +1916,78 @@ struct bt_uuid_128 { */ #define BT_UUID_CCID \ BT_UUID_DECLARE_16(BT_UUID_CCID_VAL) +/** @def BT_UUID_TBS_STATUS_FLAGS_VAL + * @brief Status flags value + */ +#define BT_UUID_TBS_STATUS_FLAGS_VAL 0x2BBB +/** @def BT_UUID_TBS_STATUS_FLAGS + * @brief Status flags + */ +#define BT_UUID_TBS_STATUS_FLAGS \ + BT_UUID_DECLARE_16(BT_UUID_TBS_STATUS_FLAGS_VAL) +/** @def BT_UUID_TBS_INCOMING_URI_VAL + * @brief Incoming Call Target Caller ID value + */ +#define BT_UUID_TBS_INCOMING_URI_VAL 0x2BBC +/** @def BT_UUID_TBS_INCOMING_URI + * @brief Incoming Call Target Caller ID + */ +#define BT_UUID_TBS_INCOMING_URI \ + BT_UUID_DECLARE_16(BT_UUID_TBS_INCOMING_URI_VAL) +/** @def BT_UUID_TBS_CALL_STATE_VAL + * @brief Call State value + */ +#define BT_UUID_TBS_CALL_STATE_VAL 0x2BBD +/** @def BT_UUID_TBS_CALL_STATE + * @brief Call State + */ +#define BT_UUID_TBS_CALL_STATE \ + BT_UUID_DECLARE_16(BT_UUID_TBS_CALL_STATE_VAL) +/** @def BT_UUID_TBS_CALL_CONTROL_POINT_VAL + * @brief Call Control Point value + */ +#define BT_UUID_TBS_CALL_CONTROL_POINT_VAL 0x2BBE +/** @def BT_UUID_TBS_CALL_CONTROL_POINT + * @brief Call Control Point + */ +#define BT_UUID_TBS_CALL_CONTROL_POINT \ + BT_UUID_DECLARE_16(BT_UUID_TBS_CALL_CONTROL_POINT_VAL) +/** @def BT_UUID_TBS_OPTIONAL_OPCODES_VAL + * @brief Optional Opcodes value + */ +#define BT_UUID_TBS_OPTIONAL_OPCODES_VAL 0x2BBF +/** @def BT_UUID_TBS_OPTIONAL_OPCODES + * @brief Optional Opcodes + */ +#define BT_UUID_TBS_OPTIONAL_OPCODES \ + BT_UUID_DECLARE_16(BT_UUID_TBS_OPTIONAL_OPCODES_VAL) +/** BT_UUID_TBS_TERMINATE_REASON_VAL + * @brief Terminate reason value + */ +#define BT_UUID_TBS_TERMINATE_REASON_VAL 0x2BC0 +/** BT_UUID_TBS_TERMINATE_REASON + * @brief Terminate reason + */ +#define BT_UUID_TBS_TERMINATE_REASON \ + BT_UUID_DECLARE_16(BT_UUID_TBS_TERMINATE_REASON_VAL) +/** @def BT_UUID_TBS_INCOMING_CALL_VAL + * @brief Incoming Call value + */ +#define BT_UUID_TBS_INCOMING_CALL_VAL 0x2BC1 +/** @def BT_UUID_TBS_INCOMING_CALL + * @brief Incoming Call + */ +#define BT_UUID_TBS_INCOMING_CALL \ + BT_UUID_DECLARE_16(BT_UUID_TBS_INCOMING_CALL_VAL) +/** @def BT_UUID_TBS_FRIENDLY_NAME_VAL + * @brief Incoming Call Friendly name value + */ +#define BT_UUID_TBS_FRIENDLY_NAME_VAL 0x2BC2 +/** @def BT_UUID_TBS_FRIENDLY_NAME + * @brief Incoming Call Friendly name + */ +#define BT_UUID_TBS_FRIENDLY_NAME \ + BT_UUID_DECLARE_16(BT_UUID_TBS_FRIENDLY_NAME_VAL) /** @def BT_UUID_MICS_MUTE_VAL * @brief Microphone Input Control Service Mute value */ diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index f013aaee3ab..181eed2041f 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -32,6 +32,8 @@ if (CONFIG_BT_CSIS OR CONFIG_BT_CSIS_CLIENT) zephyr_library_sources(csis_crypto.c) endif() +zephyr_library_sources_ifdef(CONFIG_BT_TBS tbs.c) + zephyr_library_sources_ifdef(CONFIG_BT_MCC mcc.c) zephyr_library_sources_ifdef(CONFIG_BT_MCS mcs.c) diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index a446cf9d2f1..43576c17aae 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -31,6 +31,7 @@ rsource "Kconfig.aics" rsource "Kconfig.vcs" rsource "Kconfig.mics" rsource "Kconfig.csis" +rsource "Kconfig.tbs" rsource "Kconfig.mcs" rsource "Kconfig.bass" rsource "Kconfig.has" diff --git a/subsys/bluetooth/audio/Kconfig.tbs b/subsys/bluetooth/audio/Kconfig.tbs new file mode 100644 index 00000000000..d51b4422f2a --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.tbs @@ -0,0 +1,154 @@ +# Bluetooth Audio - Call control configuration options +# +# Copyright (c) 2020 Bose Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +if BT_AUDIO + +##################### Telephone Bearer Service ##################### + +config BT_TBS + bool "Telephone Bearer Service Support" + select BT_CCID + help + This option enables support for Telephone Bearer Service. + +if BT_TBS + +# TODO: BT_GTBS is mandatory if you support the call control server role. +# Need to enforce this. +config BT_GTBS + bool "Generic Telephone Bearer Service Support" + default y + help + This option enables support for Generic Telephone Bearer Service. + +config BT_TBS_PROVIDER_NAME + string "Telephone Bearer Service Provider Name" + default "Unknown" + help + Sets the name of the service provider for the bearer. + +config BT_TBS_UCI + string "Telephone Bearer Service Uniform Caller Identifier (UCI)" + default "un000" + help + Sets the UCI of the bearer. See + https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers/ + for a table of valid UCIs. + +config BT_TBS_TECHNOLOGY + int "Telephone Bearer Service Technology" + range 1 10 + help + Sets the technology used for the bearer, e.g. GSM, LTE and 5G. + 1 : 3G + 2 : 4G + 3 : LTE + 4 : Wi-Fi + 5 : 5G + 6 : GSM + 7 : CDMA + 8 : 2G + 9 : WCDMA + 10: IP + +config BT_TBS_URI_SCHEMES_LIST + string "Telephone Bearer Service URI schemes Supported List" + default "tel,skype" + help + Sets a list of URI schemes that are supported by the bearer, + e.g. "tel" or "skype". + Multiple values shall be comma (,) separated, e.g. "tel,skype". + +config BT_TBS_SIGNAL_STRENGTH_INTERVAL + int "Telephone Bearer Service Signal Strength Reporting Interval" + default 0 + range 0 255 + help + Sets the interval of reporting the signal strength in seconds. + If the value is 0, the signal will not be reported. + +config BT_TBS_STATUS_FLAGS + int "Telephone Bearer Service Features and Status value" + default 0 + range 0 3 + help + Bitfield to set feature and status flags. + Bit 0: In-band ringtone + Bit 1: Silent mode + Bits 2-15: Reserved for future use + +config BT_TBS_SUPPORTED_FEATURES + int "Telephone Bearer Service Supported Features" + default 1 + range 0 3 + help + Bitfield to set supported features of the bearer. + Bit 0: Local Hold and Retrieve + Bit 1: Join calls within Telephone Bearer Service + +config BT_TBS_MAX_CALLS + int "Telephone Bearer Service Maximum Number Of Calls Supported" + default 3 + range 1 16 + help + Sets the maximum number of calls the service supports per bearer. + +config BT_TBS_BEARER_COUNT + int "How many bearer instances the device instantiates" + default 1 + range 1 255 + help + Sets the number of TBS instances that are instantiated + +config BT_TBS_SERVICE_COUNT + int "Number of instantiated bearer service instances" + default BT_TBS_BEARER_COUNT + range 0 BT_TBS_BEARER_COUNT if BT_GTBS + range BT_TBS_BEARER_COUNT BT_TBS_BEARER_COUNT + help + Sets the number of TBS service instances that are instantiated + +config BT_TBS_MAX_SCHEME_LIST_LENGTH + int "The maximum length of the URI scheme list" + default 30 + range 0 512 + help + Sets the maximum length of the URI scheme list. If BT_GTBS is enabled, + then the maximum length should be maximum 512 / BT_TBS_BEARER_COUNT. + +config BT_TBS_AUTHORIZATION + bool "TBS authorization requirement" + help + If set to true, then any writable characteristics will require + authorization per connection. + +config BT_TBS_MAX_URI_LENGTH + int "The maximum length of the call URI supported" + default 30 + range 4 253 + help + Sets the maximum length of the call URI supported. Note that if this + value is lower than a call URI, the call request will be rejected. + +config BT_TBS_MAX_PROVIDER_NAME_LENGTH + int "The maximum length of the bearer provider name" + default 30 + range 0 512 + help + Sets the maximum length of the bearer provider name. + +############# DEBUG ############# + +config BT_DEBUG_TBS + bool "Telephone Bearer Service debug" + help + Use this option to enable Telephone Bearer Service debug logs for the + Bluetooth Audio functionality. + +endif # BT_TBS + +endif # BT_AUDIO diff --git a/subsys/bluetooth/audio/tbs.c b/subsys/bluetooth/audio/tbs.c new file mode 100644 index 00000000000..9ad4b7d96ec --- /dev/null +++ b/subsys/bluetooth/audio/tbs.c @@ -0,0 +1,2484 @@ +/* Bluetooth TBS - Telephone Bearer Service + * + * Copyright (c) 2020 Bose Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "tbs_internal.h" +#include "ccid_internal.h" + +#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_TBS) +#define LOG_MODULE_NAME bt_tbs +#include "common/log.h" + +#define BT_TBS_VALID_STATUS_FLAGS(val) ((val) <= (BIT(0) | BIT(1))) + +/* TODO: Have tbs_service_inst include gtbs_service_inst and use CONTAINER_OF + * to get a specific TBS instance from a GTBS pointer. + */ +struct tbs_service_inst { + /* Attribute values */ + char provider_name[CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH]; + char uci[BT_TBS_MAX_UCI_SIZE]; + char uri_scheme_list[CONFIG_BT_TBS_MAX_SCHEME_LIST_LENGTH]; + 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_terminate_reason terminate_reason; + struct bt_tbs_in_uri friendly_name; + struct bt_tbs_in_uri in_call; + + /* Instance values */ + uint8_t index; + 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; + + /* TODO: The TBS (service) and the Telephone Bearers should be separated + * into two different instances. This is due to the addition of GTBS, + * where we now are in a state where this isn't a 1-to-1 correlation + * between TBS and the Telephone Bearers + */ + struct bt_gatt_service *service_p; +}; + +struct gtbs_service_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; + + /* Instance values */ + bool notify_current_calls; + bool notify_call_states; + bool pending_signal_strength_notification; + struct k_work_delayable reporting_interval_work; + + /* TODO: The TBS (service) and the Telephone Bearers should be separated + * into two different instances. This is due to the addition of GTBS, + * where we now are in a state where this isn't a 1-to-1 correlation + * between TBS and the Telephone Bearers + */ + const struct bt_gatt_service_static *service_p; +}; + +#if IS_ENABLED(CONFIG_BT_GTBS) +#define READ_BUF_SIZE (CONFIG_BT_TBS_MAX_CALLS * \ + sizeof(struct bt_tbs_current_call_item) * \ + CONFIG_BT_TBS_BEARER_COUNT) +#else +#define READ_BUF_SIZE (CONFIG_BT_TBS_MAX_CALLS * \ + sizeof(struct bt_tbs_current_call_item)) +#endif /* IS_ENABLED(CONFIG_BT_GTBS) */ +NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, READ_BUF_SIZE); + +static struct tbs_service_inst svc_insts[CONFIG_BT_TBS_BEARER_COUNT]; +static struct gtbs_service_inst gtbs_inst; + +/* 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 struct bt_tbs_call *lookup_call_in_inst(struct tbs_service_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) +{ + + if (call_index == BT_TBS_FREE_CALL_INDEX) { + return NULL; + } + + for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) { + struct bt_tbs_call *call = lookup_call_in_inst(&svc_insts[i], + call_index); + + if (call != NULL) { + return call; + } + } + + return NULL; +} + +static struct tbs_service_inst *lookup_inst_by_ccc(const struct bt_gatt_attr *ccc) +{ + if (ccc == NULL) { + return NULL; + } + + for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) { + struct tbs_service_inst *inst = &svc_insts[i]; + + if (inst->service_p == NULL) { + continue; + } + + for (size_t j = 0; j < inst->service_p->attr_count; j++) { + if (inst->service_p->attrs[j].user_data == ccc->user_data) { + return inst; + } + } + } + + return NULL; +} + +static struct tbs_service_inst *lookup_inst_by_call_index(uint8_t call_index) +{ + if (call_index == BT_TBS_FREE_CALL_INDEX) { + return NULL; + } + + 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(struct bt_conn *conn) +{ + if (IS_ENABLED(CONFIG_BT_TBS_AUTHORIZATION)) { + 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_service_inst *lookup_inst_by_uri_scheme(const char *uri, + uint8_t uri_len) +{ + char uri_scheme[CONFIG_BT_TBS_MAX_URI_LENGTH] = { 0 }; + + /* Look for ':' between the first and last char */ + for (int i = 1; i < uri_len - 1; i++) { + if (uri[i] == ':') { + (void)memcpy(uri_scheme, uri, i); + } + } + + if (strlen(uri_scheme) == 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]; + } + } + } + + return NULL; +} + +static struct tbs_service_inst *lookup_inst_by_work(const struct k_work *work) +{ + if (work == NULL) { + return NULL; + } + + for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { + if (&svc_insts[i].reporting_interval_work.work == work) { + return &svc_insts[i]; + } + } + + return NULL; +} + +static void tbs_set_terminate_reason(struct tbs_service_inst *inst, + uint8_t call_index, uint8_t reason) +{ + inst->terminate_reason.call_index = call_index; + inst->terminate_reason.reason = reason; + BT_DBG("Index %u: call index 0x%02x, reason %s", + inst->index, call_index, bt_tbs_term_reason_str(reason)); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_TERMINATE_REASON, + inst->service_p->attrs, + (void *)&inst->terminate_reason, + sizeof(inst->terminate_reason)); + + if (IS_ENABLED(CONFIG_BT_GTBS)) { + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_TERMINATE_REASON, + gtbs_inst.service_p->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 + * + * @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 = 1; + const struct bt_tbs_call *call = lookup_call(next_call_index); + + if (call == NULL) { + return next_call_index++; + } + + next_call_index++; + if (next_call_index == UINT8_MAX) { + /* call_index = 0 reserved for outgoing calls */ + next_call_index = 1; + } + } + + BT_DBG("No more free call spots"); + + return BT_TBS_FREE_CALL_INDEX; +} + +static void net_buf_put_call_state(const void *inst_p) +{ + const struct bt_tbs_call *call; + const struct bt_tbs_call *calls; + size_t call_count; + + if (inst_p == NULL) { + return; + } + + net_buf_simple_reset(&read_buf); + + if (IS_ENABLED(CONFIG_BT_GTBS) && inst_p == >bs_inst) { + for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { + calls = svc_insts[i].calls; + call_count = ARRAY_SIZE(svc_insts[i].calls); + + for (size_t j = 0; j < call_count; j++) { + call = &calls[j]; + if (call->index == BT_TBS_FREE_CALL_INDEX) { + continue; + } + + net_buf_simple_add_u8(&read_buf, call->index); + net_buf_simple_add_u8(&read_buf, call->state); + net_buf_simple_add_u8(&read_buf, call->flags); + } + + } + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)inst_p; + + calls = inst->calls; + call_count = ARRAY_SIZE(inst->calls); + + for (int i = 0; i < call_count; i++) { + call = &calls[i]; + if (call->index == BT_TBS_FREE_CALL_INDEX) { + continue; + } + + net_buf_simple_add_u8(&read_buf, call->index); + net_buf_simple_add_u8(&read_buf, call->state); + net_buf_simple_add_u8(&read_buf, call->flags); + } + } +} + +static void net_buf_put_current_calls(const void *inst_p) +{ + const struct bt_tbs_call *call; + const struct bt_tbs_call *calls; + size_t call_count; + size_t uri_length; + size_t item_len; + + if (inst_p == NULL) { + return; + } + + net_buf_simple_reset(&read_buf); + + if (IS_ENABLED(CONFIG_BT_GTBS) && inst_p == >bs_inst) { + for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { + calls = svc_insts[i].calls; + call_count = ARRAY_SIZE(svc_insts[i].calls); + + for (size_t j = 0; j < call_count; j++) { + call = &calls[j]; + if (call->index == BT_TBS_FREE_CALL_INDEX) { + continue; + } + uri_length = strlen(call->remote_uri); + item_len = sizeof(call->index != BT_TBS_FREE_CALL_INDEX) + + sizeof(call->state) + + sizeof(call->flags) + + uri_length; + net_buf_simple_add_u8(&read_buf, item_len); + net_buf_simple_add_u8(&read_buf, call->index); + net_buf_simple_add_u8(&read_buf, call->state); + net_buf_simple_add_u8(&read_buf, call->flags); + net_buf_simple_add_mem(&read_buf, + call->remote_uri, + uri_length); + } + + } + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)inst_p; + + 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 != BT_TBS_FREE_CALL_INDEX) + + sizeof(call->state) + + sizeof(call->flags) + uri_length; + net_buf_simple_add_u8(&read_buf, item_len); + net_buf_simple_add_u8(&read_buf, call->index); + net_buf_simple_add_u8(&read_buf, call->state); + net_buf_simple_add_u8(&read_buf, call->flags); + net_buf_simple_add_mem(&read_buf, call->remote_uri, + uri_length); + } + } +} + +static int notify_calls(const struct tbs_service_inst *inst) +{ + int err = 0; + + if (inst == NULL) { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_GTBS)) { + if (gtbs_inst.notify_call_states) { + net_buf_put_call_state(>bs_inst); + + err = bt_gatt_notify_uuid(NULL, BT_UUID_TBS_CALL_STATE, + gtbs_inst.service_p->attrs, + read_buf.data, read_buf.len); + if (err != 0) { + return err; + } + } + + if (gtbs_inst.notify_current_calls) { + net_buf_put_current_calls(>bs_inst); + + err = bt_gatt_notify_uuid( + NULL, BT_UUID_TBS_LIST_CURRENT_CALLS, + gtbs_inst.service_p->attrs, + read_buf.data, read_buf.len); + if (err != 0) { + return err; + } + } + } + + if (inst->notify_call_states) { + net_buf_put_call_state(inst); + + err = bt_gatt_notify_uuid(NULL, BT_UUID_TBS_CALL_STATE, + inst->service_p->attrs, + read_buf.data, read_buf.len); + if (err != 0) { + return err; + } + } + if (inst->notify_current_calls) { + net_buf_put_current_calls(inst); + + err = bt_gatt_notify_uuid(NULL, BT_UUID_TBS_LIST_CURRENT_CALLS, + inst->service_p->attrs, + read_buf.data, read_buf.len); + if (err != 0) { + return err; + } + } + + return err; +} + +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 char *provider_name; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + provider_name = gtbs_inst.provider_name; + BT_DBG("GTBS: Provider name %s", log_strdup(provider_name)); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + provider_name = inst->provider_name; + BT_DBG("Index %u, Provider name %s", + inst->index, log_strdup(provider_name)); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + provider_name, + strlen(provider_name)); +} + +static void provider_name_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + const struct tbs_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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 char *uci; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + uci = gtbs_inst.uci; + BT_DBG("GTBS: UCI %s", log_strdup(uci)); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + uci = inst->uci; + BT_DBG("Index %u: UCI %s", inst->index, log_strdup(uci)); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + uci, strlen(uci)); +} + +static ssize_t read_technology(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + uint8_t technology; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + technology = gtbs_inst.technology; + BT_DBG("GTBS: Technology 0x%02X", technology); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + technology = inst->technology; + BT_DBG("Index %u: Technology 0x%02X", inst->index, technology); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &technology, sizeof(technology)); +} + +static void technology_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + const struct tbs_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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) +{ + net_buf_simple_reset(&read_buf); + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_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) { + BT_WARN("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); + } + /* Add null terminator for printing */ + read_buf.data[read_buf.len] = '\0'; + BT_DBG("GTBS: URI scheme %s", log_strdup(read_buf.data)); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + net_buf_simple_add_mem(&read_buf, inst->uri_scheme_list, + strlen(inst->uri_scheme_list)); + /* Add null terminator for printing */ + read_buf.data[read_buf.len] = '\0'; + BT_DBG("Index %u: URI scheme %s", inst->index, + log_strdup(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_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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) +{ + uint8_t signal_strength; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + signal_strength = gtbs_inst.signal_strength; + BT_DBG("GTBS: Signal strength 0x%02x", signal_strength); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + signal_strength = inst->signal_strength; + BT_DBG("Index %u: Signal strength 0x%02x", + inst->index, signal_strength); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &signal_strength, sizeof(signal_strength)); +} + +static void signal_strength_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + const struct tbs_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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) +{ + uint8_t signal_strength_interval; + + if (!is_authorized(conn)) { + return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); + } + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + signal_strength_interval = gtbs_inst.signal_strength_interval; + BT_DBG("GTBS: Signal strength interval 0x%02x", + signal_strength_interval); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + signal_strength_interval = inst->signal_strength_interval; + BT_DBG("Index %u: Signal strength interval 0x%02x", + inst->index, signal_strength_interval); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &signal_strength_interval, + sizeof(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 net_buf_simple net_buf; + uint8_t signal_strength_interval; + + if (!is_authorized(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); + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + gtbs_inst.signal_strength_interval = signal_strength_interval; + BT_DBG("GTBS: 0x%02x", signal_strength_interval); + } else { + struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + inst->signal_strength_interval = signal_strength_interval; + BT_DBG("Index %u: 0x%02x", + inst->index, signal_strength_interval); + } + + return len; +} + +static void current_calls_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + inst->notify_current_calls = (value == BT_GATT_CCC_NOTIFY); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", value); + gtbs_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) +{ + net_buf_put_current_calls(attr->user_data); + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + BT_DBG("GTBS"); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + BT_DBG("Index %u", inst->index); + } + + if (offset == 0) { + BT_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) +{ + uint8_t ccid; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + ccid = gtbs_inst.ccid; + BT_DBG("GTBS: CCID 0x%02X", ccid); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + ccid = inst->ccid; + BT_DBG("Index %u: CCID 0x%02X", inst->index, ccid); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &ccid, sizeof(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) +{ + uint16_t status_flags; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + status_flags = gtbs_inst.status_flags; + BT_DBG("GTBS: status_flags 0x%04X", status_flags); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + status_flags = inst->status_flags; + BT_DBG("Index %u: status_flags 0x%04X", + inst->index, status_flags); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &status_flags, sizeof(status_flags)); +} + +static void status_flags_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + const struct tbs_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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 bt_tbs_in_uri *inc_call_target; + size_t val_len; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + inc_call_target = >bs_inst.incoming_uri; + BT_DBG("GTBS: call index 0x%02X, URI %s", + inc_call_target->call_index, + log_strdup(inc_call_target->uri)); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + inc_call_target = &inst->incoming_uri; + BT_DBG("Index %u: call index 0x%02X, URI %s", + inst->index, inc_call_target->call_index, + log_strdup(inc_call_target->uri)); + } + + if (!inc_call_target->call_index) { + BT_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_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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) +{ + net_buf_put_call_state(attr->user_data); + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + BT_DBG("GTBS"); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + BT_DBG("Index %u", inst->index); + } + + if (offset == 0) { + BT_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_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + inst->notify_call_states = (value == BT_GATT_CCC_NOTIFY); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", value); + gtbs_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 + }; + + BT_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_service_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_service_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_service_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->index = BT_TBS_FREE_CALL_INDEX; + tbs_set_terminate_reason(inst, ccp->call_index, reason); + + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static uint8_t tbs_hold_call(struct tbs_service_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_service_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_service_inst *inst, + const struct bt_tbs_call_cp_originate *ccp, + uint16_t uri_len, uint8_t *call_index) +{ + struct bt_tbs_call *call = NULL; + + /* New call - Look for unused call item */ + for (int i = 0; i < CONFIG_BT_TBS_MAX_CALLS; i++) { + if (inst->calls[i].index == BT_TBS_FREE_CALL_INDEX) { + call = &inst->calls[i]; + break; + } + } + + /* 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 (call == NULL) { + return BT_TBS_RESULT_CODE_OUT_OF_RESOURCES; + } + + call->index = next_free_call_index(); + + if (call->index == BT_TBS_FREE_CALL_INDEX) { + return BT_TBS_RESULT_CODE_OUT_OF_RESOURCES; + } + + if (uri_len == 0 || uri_len > CONFIG_BT_TBS_MAX_URI_LENGTH) { + call->index = BT_TBS_FREE_CALL_INDEX; + return BT_TBS_RESULT_CODE_INVALID_URI; + } + + (void)memcpy(call->remote_uri, ccp->uri, uri_len); + call->remote_uri[uri_len] = '\0'; + if (!bt_tbs_valid_uri(call->remote_uri)) { + BT_DBG("Invalid URI: %s", log_strdup(call->remote_uri)); + call->index = BT_TBS_FREE_CALL_INDEX; + + return BT_TBS_RESULT_CODE_INVALID_URI; + } + + /* We need to notify dialing state for test, + * even though we don't have an internal dialing state. + */ + call->state = BT_TBS_CALL_STATE_DIALING; + if (call->index != BT_TBS_FREE_CALL_INDEX) { + *call_index = call->index; + } + BT_TBS_CALL_FLAG_SET_OUTGOING(call->flags); + + hold_other_calls(inst, 1, &call->index); + notify_calls(inst); + + BT_DBG("New call with call index %u", call->index); + + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static uint8_t join_calls(struct tbs_service_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, 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) { + const struct tbs_service_inst *inst = + lookup_inst_by_call_index(ccp->terminate.call_index); + + 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; + struct tbs_service_inst *inst; + + inst = lookup_inst_by_call_index(call_index); + + if (inst == NULL) { + BT_DBG("Could not find instance by call index 0x%02X", + call_index); + break; + } + + call = lookup_call_in_inst(inst, call_index); + + if (call == NULL) { + BT_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_service_inst *inst = NULL; + const union bt_tbs_call_cp_t *ccp = (union bt_tbs_call_cp_t *)buf; + uint8_t status; + uint8_t call_index = 0; + const bool is_gtbs = IS_ENABLED(CONFIG_BT_GTBS) && + attr->user_data == >bs_inst; + + if (!is_authorized(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); + } + + if (is_gtbs) { + BT_DBG("GTBS: Processing the %s opcode", + bt_tbs_opcode_str(ccp->opcode)); + } else { + inst = (struct tbs_service_inst *)attr->user_data; + BT_DBG("Index %u: Processing the %s opcode", + inst->index, 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) { + inst = lookup_inst_by_call_index(call_index); + if (inst == NULL) { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + + status = accept_call(inst, &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) { + inst = lookup_inst_by_call_index(call_index); + if (inst == NULL) { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + + status = terminate_call(inst, &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) { + inst = lookup_inst_by_call_index(call_index); + if (inst == NULL) { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + + status = tbs_hold_call(inst, &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) { + inst = lookup_inst_by_call_index(call_index); + if (inst == NULL) { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + + status = retrieve_call(inst, &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) { + inst = lookup_inst_by_uri_scheme(ccp->originate.uri, + uri_len); + if (inst == NULL) { + /* TODO: Couldn't find fitting TBS instance; + * use the first. If we want to be + * restrictive about URIs, return + * Invalid Caller ID instead + */ + inst = &svc_insts[0]; + } + } + + status = originate_call(inst, &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) { + inst = lookup_inst_by_call_index(call_index); + if (inst == NULL) { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + + status = join_calls(inst, &ccp->join, call_index_cnt); + break; + } + default: + status = BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; + call_index = 0; + break; + } + + if (inst != NULL) { + if (is_gtbs) { + BT_DBG("GTBS: Processed the %s opcode with status %s " + "for call index %u", + bt_tbs_opcode_str(ccp->opcode), + bt_tbs_status_str(status), + call_index); + } else { + BT_DBG("Index %u: Processed the %s opcode with status " + "%s for call index %u", + inst->index, + 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) { + BT_DBG("Call is now in the %s state", + bt_tbs_state_str(call->state)); + } else { + BT_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 (status == BT_TBS_RESULT_CODE_SUCCESS) { + notify_calls(inst); + notify_app(conn, 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_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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) +{ + uint16_t optional_opcodes; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + optional_opcodes = gtbs_inst.optional_opcodes; + BT_DBG("GTBS: Supported opcodes 0x%02x", optional_opcodes); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + optional_opcodes = inst->optional_opcodes; + BT_DBG("Index %u: Supported opcodes 0x%02x", + inst->index, optional_opcodes); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &optional_opcodes, sizeof(optional_opcodes)); +} + +static void terminate_reason_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + const struct tbs_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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 bt_tbs_in_uri *friendly_name; + size_t val_len; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + friendly_name = >bs_inst.friendly_name; + BT_DBG("GTBS: call index 0x%02X, URI %s", + friendly_name->call_index, + log_strdup(friendly_name->uri)); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + friendly_name = &inst->friendly_name; + BT_DBG("Index %u: call index 0x%02X, URI %s", + inst->index, friendly_name->call_index, + log_strdup(friendly_name->uri)); + } + + if (friendly_name->call_index == BT_TBS_FREE_CALL_INDEX) { + BT_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_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", 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 bt_tbs_in_uri *remote_uri; + size_t val_len; + + if (IS_ENABLED(CONFIG_BT_GTBS) && attr->user_data == >bs_inst) { + remote_uri = >bs_inst.in_call; + BT_DBG("GTBS: call index 0x%02X, URI %s", + remote_uri->call_index, log_strdup(remote_uri->uri)); + } else { + const struct tbs_service_inst *inst = (struct tbs_service_inst *)attr->user_data; + + remote_uri = &inst->in_call; + BT_DBG("Index %u: call index 0x%02X, URI %s", + inst->index, remote_uri->call_index, + log_strdup(remote_uri->uri)); + } + + if (remote_uri->call_index == BT_TBS_FREE_CALL_INDEX) { + BT_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_service_inst *inst = lookup_inst_by_ccc(attr); + + if (inst != NULL) { + BT_DBG("Index %u: value 0x%04x", inst->index, value); + } else if (IS_ENABLED(CONFIG_BT_GTBS)) { + BT_DBG("GTBS: value 0x%04x", value); + } +} + +#define BT_TBS_CHR_PROVIDER_NAME(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_TBS_PROVIDER_NAME, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_provider_name, NULL, inst), \ + BT_GATT_CCC(provider_name_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_UCI(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_TBS_UCI, \ + BT_GATT_CHRC_READ, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_uci, NULL, inst) + +#define BT_TBS_CHR_TECHNOLOGY(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_TBS_TECHNOLOGY, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_technology, NULL, inst), \ + BT_GATT_CCC(technology_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_URI_LIST(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CCC(uri_scheme_list_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_SIGNAL_STRENGTH(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CCC(signal_strength_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_SIGNAL_INTERVAL(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CHARACTERISTIC(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_GATT_CCC(current_calls_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_CCID(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_CCID, \ + BT_GATT_CHRC_READ, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_ccid, NULL, inst) + +#define BT_TBS_CHR_STATUS_FLAGS(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_TBS_STATUS_FLAGS, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_status_flags, NULL, inst), \ + BT_GATT_CCC(status_flags_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_INCOMING_URI(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CCC(incoming_uri_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_CALL_STATE(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_TBS_CALL_STATE, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_call_state, NULL, inst), \ + BT_GATT_CCC(call_state_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_CONTROL_POINT(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CCC(call_cp_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_OPTIONAL_OPCODES(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CHARACTERISTIC(BT_UUID_TBS_TERMINATE_REASON, \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + NULL, NULL, inst), \ + BT_GATT_CCC(terminate_reason_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_INCOMING_CALL(inst) \ + BT_GATT_CHARACTERISTIC(BT_UUID_TBS_INCOMING_CALL, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_incoming_call, NULL, inst), \ + BT_GATT_CCC(in_call_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_CHR_FRIENDLY_NAME(inst) \ + BT_GATT_CHARACTERISTIC(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_GATT_CCC(friendly_name_cfg_changed, \ + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) + +#define BT_TBS_SERVICE_DEFINITION(inst) {\ + BT_GATT_PRIMARY_SERVICE(BT_UUID_TBS), \ + 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_GTBS_SERVICE_DEFINITION(inst) \ + BT_GATT_PRIMARY_SERVICE(BT_UUID_GTBS), \ + 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) + +/* + * Defining this as extern make it possible to link code that otherwise would + * give "unknown identifier" linking errors. + */ +extern const struct bt_gatt_service_static gtbs_svc; + +/* TODO: Can we make the multiple service instance more generic? */ +#if CONFIG_BT_GTBS +BT_GATT_SERVICE_DEFINE(gtbs_svc, BT_GTBS_SERVICE_DEFINITION(>bs_inst)); +#endif /* CONFIG_BT_GTBS */ + +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 tbs_service_inst *inst = lookup_inst_by_work(work); + + if (inst && inst->pending_signal_strength_notification) { + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_SIGNAL_STRENGTH, + inst->service_p->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; + } else if (IS_ENABLED(CONFIG_BT_GTBS) && + gtbs_inst.pending_signal_strength_notification) { + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_SIGNAL_STRENGTH, + gtbs_inst.service_p->attrs, + >bs_inst.signal_strength, + sizeof(gtbs_inst.signal_strength)); + + if (gtbs_inst.signal_strength_interval) { + k_work_reschedule( + >bs_inst.reporting_interval_work, + K_SECONDS(gtbs_inst.signal_strength_interval)); + } + + gtbs_inst.pending_signal_strength_notification = false; + } +} + +static int bt_tbs_init(const struct device *unused) +{ + for (size_t i = 0; i < ARRAY_SIZE(svc_insts); i++) { + int err; + + svc_insts[i].service_p = &tbs_service_list[i]; + + err = bt_gatt_service_register(svc_insts[i].service_p); + if (err != 0) { + BT_ERR("Could not register TBS[%d]: %d", i, err); + } + } + + if (IS_ENABLED(CONFIG_BT_GTBS)) { + gtbs_inst.service_p = >bs_svc; + (void)strcpy(gtbs_inst.provider_name, "Generic TBS"); + gtbs_inst.optional_opcodes = CONFIG_BT_TBS_SUPPORTED_FEATURES; + gtbs_inst.ccid = bt_ccid_get_value(); + (void)strcpy(gtbs_inst.uci, "un000"); + + k_work_init_delayable(>bs_inst.reporting_interval_work, + signal_interval_timeout); + } + + for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) { + /* Init default values */ + svc_insts[i].index = i; + svc_insts[i].ccid = bt_ccid_get_value(); + (void)strcpy(svc_insts[i].provider_name, + CONFIG_BT_TBS_PROVIDER_NAME); + (void)strcpy(svc_insts[i].uci, CONFIG_BT_TBS_UCI); + (void)strcpy(svc_insts[i].uri_scheme_list, + CONFIG_BT_TBS_URI_SCHEMES_LIST); + svc_insts[i].optional_opcodes = CONFIG_BT_TBS_SUPPORTED_FEATURES; + svc_insts[i].technology = CONFIG_BT_TBS_TECHNOLOGY; + svc_insts[i].signal_strength_interval = CONFIG_BT_TBS_SIGNAL_STRENGTH_INTERVAL; + svc_insts[i].status_flags = CONFIG_BT_TBS_STATUS_FLAGS; + + k_work_init_delayable(&svc_insts[i].reporting_interval_work, + signal_interval_timeout); + } + + return 0; +} + +DEVICE_DEFINE(bt_tbs, "bt_tbs", &bt_tbs_init, NULL, NULL, NULL, + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL); + +/***************************** Profile API *****************************/ +int bt_tbs_accept(uint8_t call_index) +{ + struct tbs_service_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_service_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); + } + + return status; +} + +int bt_tbs_retrieve(uint8_t call_index) +{ + struct tbs_service_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); + } + + return status; +} + +int bt_tbs_terminate(uint8_t call_index) +{ + struct tbs_service_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); + } + + return status; +} + +int bt_tbs_originate(uint8_t bearer_index, char *remote_uri, + uint8_t *call_index) +{ + struct tbs_service_inst *inst; + 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 (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + return -EINVAL; + } else if (!bt_tbs_valid_uri(remote_uri)) { + BT_DBG("Invalid URI %s", log_strdup(remote_uri)); + return -EINVAL; + } + + uri_len = strlen(remote_uri); + + inst = &svc_insts[bearer_index]; + + ccp->opcode = BT_TBS_CALL_OPCODE_ORIGINATE; + (void)memcpy(ccp->uri, remote_uri, uri_len); + + return originate_call(inst, ccp, uri_len, call_index); +} + +int bt_tbs_join(uint8_t call_index_cnt, uint8_t *call_indexes) +{ + struct tbs_service_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_service_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_service_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_service_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_service_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); + } + + return status; +} + +int bt_tbs_remote_incoming(uint8_t bearer_index, const char *to, + const char *from, const char *friendly_name) +{ + struct tbs_service_inst *inst; + struct bt_tbs_call *call = NULL; + size_t local_uri_ind_len; + size_t remote_uri_ind_len; + size_t friend_name_ind_len; + + if (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + return -EINVAL; + } else if (!bt_tbs_valid_uri(to)) { + BT_DBG("Invalid \"to\" URI: %s", log_strdup(to)); + return -EINVAL; + } else if (!bt_tbs_valid_uri(from)) { + BT_DBG("Invalid \"from\" URI: %s", log_strdup(from)); + return -EINVAL; + } + + local_uri_ind_len = strlen(to) + 1; + remote_uri_ind_len = strlen(from) + 1; + + inst = &svc_insts[bearer_index]; + + /* New call - Look for unused call item */ + for (int i = 0; i < CONFIG_BT_TBS_MAX_CALLS; i++) { + if (inst->calls[i].index == BT_TBS_FREE_CALL_INDEX) { + call = &inst->calls[i]; + break; + } + } + + if (call == NULL) { + return -BT_TBS_RESULT_CODE_OUT_OF_RESOURCES; + } + + call->index = next_free_call_index(); + + if (call->index == BT_TBS_FREE_CALL_INDEX) { + return -BT_TBS_RESULT_CODE_OUT_OF_RESOURCES; + } + + BT_TBS_CALL_FLAG_SET_INCOMING(call->flags); + + (void)strcpy(call->remote_uri, from); + call->state = BT_TBS_CALL_STATE_INCOMING; + + inst->in_call.call_index = call->index; + (void)strcpy(inst->in_call.uri, from); + + inst->incoming_uri.call_index = call->index; + (void)strcpy(inst->incoming_uri.uri, to); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_INCOMING_URI, + inst->service_p->attrs, + &inst->incoming_uri, local_uri_ind_len); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_INCOMING_CALL, + inst->service_p->attrs, + &inst->in_call, remote_uri_ind_len); + + if (friendly_name) { + inst->friendly_name.call_index = call->index; + (void)strcpy(inst->friendly_name.uri, friendly_name); + friend_name_ind_len = strlen(from) + 1; + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_FRIENDLY_NAME, + inst->service_p->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->service_p->attrs, NULL, 0); + } + + if (IS_ENABLED(CONFIG_BT_GTBS)) { + gtbs_inst.in_call.call_index = call->index; + (void)strcpy(gtbs_inst.in_call.uri, from); + + gtbs_inst.incoming_uri.call_index = call->index; + (void)strcpy(gtbs_inst.incoming_uri.uri, to); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_INCOMING_URI, + gtbs_inst.service_p->attrs, + >bs_inst.incoming_uri, local_uri_ind_len); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_INCOMING_CALL, + gtbs_inst.service_p->attrs, + >bs_inst.in_call, remote_uri_ind_len); + + if (friendly_name) { + gtbs_inst.friendly_name.call_index = call->index; + (void)strcpy(gtbs_inst.friendly_name.uri, friendly_name); + friend_name_ind_len = strlen(from) + 1; + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_FRIENDLY_NAME, + gtbs_inst.service_p->attrs, + >bs_inst.friendly_name, + friend_name_ind_len); + } else { + gtbs_inst.friendly_name.call_index = 0; + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_FRIENDLY_NAME, + gtbs_inst.service_p->attrs, + NULL, 0); + } + } + + notify_calls(inst); + + BT_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) +{ + const size_t len = strlen(name); + const struct bt_gatt_attr *attr; + + if (len >= CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH || len == 0) { + return -EINVAL; + } else if (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + if (!(IS_ENABLED(CONFIG_BT_GTBS) && + bearer_index == BT_TBS_GTBS_INDEX)) { + return -EINVAL; + } + } + + if (bearer_index == BT_TBS_GTBS_INDEX) { + if (strcmp(gtbs_inst.provider_name, name) == 0) { + return 0; + } + + (void)strcpy(gtbs_inst.provider_name, name); + attr = gtbs_inst.service_p->attrs; + } else { + if (strcmp(svc_insts[bearer_index].provider_name, name) == 0) { + return 0; + } + + (void)strcpy(svc_insts[bearer_index].provider_name, name); + attr = svc_insts[bearer_index].service_p->attrs; + } + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_PROVIDER_NAME, + attr, name, strlen(name)); + return 0; +} + +int bt_tbs_set_bearer_technology(uint8_t bearer_index, uint8_t new_technology) +{ + const struct bt_gatt_attr *attr; + + if (new_technology < BT_TBS_TECHNOLOGY_3G || + new_technology > BT_TBS_TECHNOLOGY_IP) { + return -EINVAL; + } else if (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + if (!(IS_ENABLED(CONFIG_BT_GTBS) && + bearer_index == BT_TBS_GTBS_INDEX)) { + return -EINVAL; + } + } + + if (bearer_index == BT_TBS_GTBS_INDEX) { + if (gtbs_inst.technology == new_technology) { + return 0; + } + + gtbs_inst.technology = new_technology; + attr = gtbs_inst.service_p->attrs; + } else { + if (svc_insts[bearer_index].technology == new_technology) { + return 0; + } + + svc_insts[bearer_index].technology = new_technology; + attr = svc_insts[bearer_index].service_p->attrs; + } + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_TECHNOLOGY, + attr, &new_technology, sizeof(new_technology)); + + return 0; +} + +int bt_tbs_set_signal_strength(uint8_t bearer_index, + uint8_t new_signal_strength) +{ + const struct bt_gatt_attr *attr; + uint32_t timer_status; + uint8_t interval; + struct k_work_delayable *reporting_interval_work; + struct tbs_service_inst *inst; + + if (new_signal_strength > BT_TBS_SIGNAL_STRENGTH_MAX && + new_signal_strength != BT_TBS_SIGNAL_STRENGTH_UNKNOWN) { + return -EINVAL; + } else if (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + if (!(IS_ENABLED(CONFIG_BT_GTBS) && + bearer_index == BT_TBS_GTBS_INDEX)) { + return -EINVAL; + } + } + + if (bearer_index == BT_TBS_GTBS_INDEX) { + if (gtbs_inst.signal_strength == new_signal_strength) { + return 0; + } + + gtbs_inst.signal_strength = new_signal_strength; + attr = gtbs_inst.service_p->attrs; + timer_status = k_work_delayable_remaining_get( + >bs_inst.reporting_interval_work); + interval = gtbs_inst.signal_strength_interval; + reporting_interval_work = >bs_inst.reporting_interval_work; + } else { + inst = &svc_insts[bearer_index]; + if (inst->signal_strength == new_signal_strength) { + return 0; + } + + inst->signal_strength = new_signal_strength; + attr = inst->service_p->attrs; + timer_status = k_work_delayable_remaining_get( + &inst->reporting_interval_work); + interval = inst->signal_strength_interval; + reporting_interval_work = &inst->reporting_interval_work; + } + + if (timer_status == 0) { + const k_timeout_t delay = K_SECONDS(interval); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_SIGNAL_STRENGTH, + attr, &new_signal_strength, + sizeof(new_signal_strength)); + if (interval) { + k_work_reschedule(reporting_interval_work, delay); + } + } else { + if (bearer_index == BT_TBS_GTBS_INDEX) { + BT_DBG("GTBS: Reporting signal strength in %d ms", + timer_status); + gtbs_inst.pending_signal_strength_notification = true; + + } else { + BT_DBG("Index %u: Reporting signal strength in %d ms", + bearer_index, timer_status); + inst->pending_signal_strength_notification = true; + } + } + + return 0; +} + +int bt_tbs_set_status_flags(uint8_t bearer_index, uint16_t status_flags) +{ + const struct bt_gatt_attr *attr; + + if (!BT_TBS_VALID_STATUS_FLAGS(status_flags)) { + return -EINVAL; + } else if (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + if (!(IS_ENABLED(CONFIG_BT_GTBS) && + bearer_index == BT_TBS_GTBS_INDEX)) { + return -EINVAL; + } + } + + if (bearer_index == BT_TBS_GTBS_INDEX) { + if (gtbs_inst.status_flags == status_flags) { + return 0; + } + + gtbs_inst.status_flags = status_flags; + attr = gtbs_inst.service_p->attrs; + } else { + if (svc_insts[bearer_index].status_flags == status_flags) { + return 0; + } + + svc_insts[bearer_index].status_flags = status_flags; + attr = svc_insts[bearer_index].service_p->attrs; + } + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_STATUS_FLAGS, + attr, &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_service_inst *inst; + + if (bearer_index >= CONFIG_BT_TBS_BEARER_COUNT) { + 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)strcpy(inst->uri_scheme_list, uri_scheme_list); + + BT_DBG("TBS instance %u uri prefix list is now %s", + bearer_index, log_strdup(inst->uri_scheme_list)); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_URI_LIST, + inst->service_p->attrs, &inst->uri_scheme_list, + strlen(inst->uri_scheme_list)); + + if (IS_ENABLED(CONFIG_BT_GTBS)) { + NET_BUF_SIMPLE_DEFINE(uri_scheme_buf, READ_BUF_SIZE); + + /* TODO: Make uri schemes unique */ + for (int i = 0; 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) { + BT_WARN("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); + } + + /* Add null terminator for printing */ + uri_scheme_buf.data[uri_scheme_buf.len] = '\0'; + BT_DBG("GTBS: URI scheme %s", log_strdup(uri_scheme_buf.data)); + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_URI_LIST, + gtbs_inst.service_p->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_DEBUG_TBS) +void bt_tbs_dbg_print_calls(void) +{ + for (int i = 0; i < CONFIG_BT_TBS_BEARER_COUNT; i++) { + BT_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; + } + + BT_DBG(" Call #%u", call->index); + BT_DBG(" State: %s", bt_tbs_state_str(call->state)); + BT_DBG(" Flags: 0x%02X", call->flags); + BT_DBG(" URI : %s", log_strdup(call->remote_uri)); + } + } +} +#endif /* defined(CONFIG_BT_DEBUG_TBS) */ diff --git a/subsys/bluetooth/audio/tbs_internal.h b/subsys/bluetooth/audio/tbs_internal.h new file mode 100644 index 00000000000..03c99d935d3 --- /dev/null +++ b/subsys/bluetooth/audio/tbs_internal.h @@ -0,0 +1,283 @@ +/** @file + * @brief Internal APIs for Bluetooth TBS. + */ + +/* + * Copyright (c) 2019 Bose Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define BT_TBS_MAX_UCI_SIZE 6 +#define BT_TBS_MIN_URI_LEN 3 /* a:b */ +#define BT_TBS_FREE_CALL_INDEX 0 + +/* Call Control Point Opcodes */ +#define BT_TBS_CALL_OPCODE_ACCEPT 0x00 +#define BT_TBS_CALL_OPCODE_TERMINATE 0x01 +#define BT_TBS_CALL_OPCODE_HOLD 0x02 +#define BT_TBS_CALL_OPCODE_RETRIEVE 0x03 +#define BT_TBS_CALL_OPCODE_ORIGINATE 0x04 +#define BT_TBS_CALL_OPCODE_JOIN 0x05 + +/* Local Control Points - Used to do local control operations but still being + * able to determine if it is a local or remote operation + */ +#define BT_TBS_LOCAL_OPCODE_ANSWER 0x80 +#define BT_TBS_LOCAL_OPCODE_HOLD 0x81 +#define BT_TBS_LOCAL_OPCODE_RETRIEVE 0x82 +#define BT_TBS_LOCAL_OPCODE_TERMINATE 0x83 +#define BT_TBS_LOCAL_OPCODE_INCOMING 0x84 +#define BT_TBS_LOCAL_OPCODE_SERVER_TERMINATE 0x85 + +#define FIRST_PRINTABLE_ASCII_CHAR ' ' /* space */ + +static inline const char *bt_tbs_state_str(uint8_t state) +{ + switch (state) { + case BT_TBS_CALL_STATE_INCOMING: + return "incoming"; + case BT_TBS_CALL_STATE_DIALING: + return "dialing"; + case BT_TBS_CALL_STATE_ALERTING: + return "alerting"; + case BT_TBS_CALL_STATE_ACTIVE: + return "active"; + case BT_TBS_CALL_STATE_LOCALLY_HELD: + return "locally held"; + case BT_TBS_CALL_STATE_REMOTELY_HELD: + return "remote held"; + case BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD: + return "locally and remotely held"; + default: + return "unknown"; + } +} + +static inline const char *bt_tbs_opcode_str(uint8_t opcode) +{ + switch (opcode) { + case BT_TBS_CALL_OPCODE_ACCEPT: + return "accept"; + case BT_TBS_CALL_OPCODE_TERMINATE: + return "terminate"; + case BT_TBS_CALL_OPCODE_HOLD: + return "hold"; + case BT_TBS_CALL_OPCODE_RETRIEVE: + return "retrieve"; + case BT_TBS_CALL_OPCODE_ORIGINATE: + return "originate"; + case BT_TBS_CALL_OPCODE_JOIN: + return "join"; + case BT_TBS_LOCAL_OPCODE_ANSWER: + return "remote answer"; + case BT_TBS_LOCAL_OPCODE_HOLD: + return "remote hold"; + case BT_TBS_LOCAL_OPCODE_RETRIEVE: + return "remote retrieve"; + case BT_TBS_LOCAL_OPCODE_TERMINATE: + return "remote terminate"; + case BT_TBS_LOCAL_OPCODE_INCOMING: + return "remote incoming"; + case BT_TBS_LOCAL_OPCODE_SERVER_TERMINATE: + return "server terminate"; + default: + return "unknown"; + } +} + +static inline const char *bt_tbs_status_str(uint8_t status) +{ + switch (status) { + case BT_TBS_RESULT_CODE_SUCCESS: + return "success"; + case BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED: + return "opcode not supported"; + case BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE: + return "operation not possible"; + case BT_TBS_RESULT_CODE_INVALID_CALL_INDEX: + return "invalid call index"; + case BT_TBS_RESULT_CODE_STATE_MISMATCH: + return "state mismatch"; + case BT_TBS_RESULT_CODE_OUT_OF_RESOURCES: + return "out of resources"; + case BT_TBS_RESULT_CODE_INVALID_URI: + return "invalid URI"; + default: + return "ATT err"; + } +} + +static inline const char *bt_tbs_technology_str(uint8_t status) +{ + switch (status) { + case BT_TBS_TECHNOLOGY_3G: + return "3G"; + case BT_TBS_TECHNOLOGY_4G: + return "4G"; + case BT_TBS_TECHNOLOGY_LTE: + return "LTE"; + case BT_TBS_TECHNOLOGY_WIFI: + return "WIFI"; + case BT_TBS_TECHNOLOGY_5G: + return "5G"; + case BT_TBS_TECHNOLOGY_GSM: + return "GSM"; + case BT_TBS_TECHNOLOGY_CDMA: + return "CDMA"; + case BT_TBS_TECHNOLOGY_2G: + return "2G"; + case BT_TBS_TECHNOLOGY_WCDMA: + return "WCDMA"; + case BT_TBS_TECHNOLOGY_IP: + return "IP"; + default: + return "unknown technology"; + } +} + +static inline const char *bt_tbs_term_reason_str(uint8_t reason) +{ + switch (reason) { + case BT_TBS_REASON_BAD_REMOTE_URI: + return "bad remote URI"; + case BT_TBS_REASON_CALL_FAILED: + return "call failed"; + case BT_TBS_REASON_REMOTE_ENDED_CALL: + return "remote ended call"; + case BT_TBS_REASON_SERVER_ENDED_CALL: + return "server ended call"; + case BT_TBS_REASON_LINE_BUSY: + return "line busy"; + case BT_TBS_REASON_NETWORK_CONGESTED: + return "network congested"; + case BT_TBS_REASON_CLIENT_TERMINATED: + return "client terminated"; + case BT_TBS_REASON_UNSPECIFIED: + return "unspecified"; + default: + return "unknown reason"; + } +} + +/** + * @brief Checks if a string contains a colon (':') followed by a printable + * character. Minimal uri is "a:b". + * + * @param uri The uri "scheme:id" + * @return true If the above is true + * @return false If the above is not true + */ +static inline bool bt_tbs_valid_uri(const char *uri) +{ + size_t len; + + if (!uri) { + return false; + } + + len = strlen(uri); + if (len > CONFIG_BT_TBS_MAX_URI_LENGTH || + len < BT_TBS_MIN_URI_LEN) { + return false; + } else if (uri[0] < FIRST_PRINTABLE_ASCII_CHAR) { + /* Invalid first char */ + return false; + } + + for (int i = 1; i < len; i++) { + if (uri[i] == ':' && uri[i + 1] >= FIRST_PRINTABLE_ASCII_CHAR) { + return true; + } + } + + return false; +} + +/* TODO: The bt_tbs_call could use the bt_tbs_call_state struct for the first + * 3 fields + */ +struct bt_tbs_call { + uint8_t index; + uint8_t state; + uint8_t flags; + char remote_uri[CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; +} __packed; + +struct bt_tbs_call_state { + uint8_t index; + uint8_t state; + uint8_t flags; +} __packed; + +struct bt_tbs_call_cp_acc { + uint8_t opcode; + uint8_t call_index; +} __packed; + +struct bt_tbs_call_cp_term { + uint8_t opcode; + uint8_t call_index; +} __packed; + +struct bt_tbs_call_cp_hold { + uint8_t opcode; + uint8_t call_index; +} __packed; + +struct bt_tbs_call_cp_retrieve { + uint8_t opcode; + uint8_t call_index; +} __packed; + +struct bt_tbs_call_cp_originate { + uint8_t opcode; + uint8_t uri[0]; +} __packed; + +struct bt_tbs_call_cp_join { + uint8_t opcode; + uint8_t call_indexes[0]; +} __packed; + +union bt_tbs_call_cp_t { + uint8_t opcode; + struct bt_tbs_call_cp_acc accept; + struct bt_tbs_call_cp_term terminate; + struct bt_tbs_call_cp_hold hold; + struct bt_tbs_call_cp_retrieve retrieve; + struct bt_tbs_call_cp_originate originate; + struct bt_tbs_call_cp_join join; +} __packed; + +struct bt_tbs_call_cp_notify { + uint8_t opcode; + uint8_t call_index; + uint8_t status; +} __packed; + +struct bt_tbs_call_state_notify { + uint8_t call_index; + uint8_t state; +} __packed; + +struct bt_tbs_terminate_reason { + uint8_t call_index; + uint8_t reason; +} __packed; + +struct bt_tbs_current_call_item { + uint8_t length; + uint8_t call_index; + uint8_t call_state; + uint8_t uri[CONFIG_BT_TBS_MAX_URI_LENGTH]; +} __packed; + +struct bt_tbs_in_uri { + uint8_t call_index; + char uri[CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; +} __packed;